[Valid RSS] This is a valid RSS feed.


This feed is valid, but interoperability with the widest range of feed readers could be improved by implementing the following recommendations.


  1. <?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
  2. xmlns:content=""
  3. xmlns:wfw=""
  4. xmlns:dc=""
  5. xmlns:atom=""
  6. xmlns:sy=""
  7. xmlns:slash=""
  8. >
  10. <channel>
  11. <title>Software Development &#8211; Cameron Gregor</title>
  12. <atom:link href="" rel="self" type="application/rss+xml" />
  13. <link></link>
  14. <description>Sharing Tips, Tools and Techniques for XPages Developers</description>
  15. <lastBuildDate>Mon, 14 Nov 2016 23:22:36 +0000</lastBuildDate>
  16. <language>en-AU</language>
  17. <sy:updatePeriod>hourly</sy:updatePeriod>
  18. <sy:updateFrequency>1</sy:updateFrequency>
  19. <generator></generator>
  20. <item>
  21. <title>Preventing pasting of remotely hosted images in CKEditor</title>
  22. <link></link>
  23. <comments></comments>
  24. <pubDate>Mon, 14 Nov 2016 23:21:59 +0000</pubDate>
  25. <dc:creator><![CDATA[camerongregor]]></dc:creator>
  26. <category><![CDATA[Software Development]]></category>
  27. <category><![CDATA[ckeditor]]></category>
  28. <category><![CDATA[webmail]]></category>
  29. <category><![CDATA[xpages]]></category>
  31. <guid isPermaLink="false"></guid>
  32. <description><![CDATA[In the previous post, I showed how to prevent a user from pasting Images from the Clipboard into CKEditor. This post is of a similar nature but is designed to ensure that users don&#8217;t&#46;&#46;&#46;]]></description>
  33. <content:encoded><![CDATA[<p>In the previous post, I showed <a href="">how to prevent a user from pasting Images from the Clipboard into CKEditor</a>. This post is of a similar nature but is designed to ensure that users don&#8217;t paste images with URLs to external / internal applications.</p>
  34. <p>This post is part of my <a href="">XPages webmail tips series</a>, and addresses a problem where, a user copies and pastes some HTML that includes images, from a webpage and pastes it into CKEditor for a message that is then sent via email. The recipient is then unable to see the image due to the fact they don&#8217;t have the same access as the author of the email.</p>
  35. <p>The cause of problem is, when the image is pasted it is pasted as an img tag with a link to the location of the image on a server.<br />
  36. There is no guarantee that the email recipient can access the server that the image is located on. The server is possibly behind a firewall, OR if the HTML was copied from an internal system, then it is possible an external email recipient does not have access to that internal server.</p>
  37. <p>Additionally even for Internal emails, if the html was copied from an XPages application and the copied image is located inside a Notes Document, the URL that is used for that image is only temporarily available by the Xpages Persistence service, and is only available to the user that copied the HTML.</p>
  38. <p>The result of all of this is more complaints of &#8220;I can&#8217;t see any image&#8221;</p>
  39. <p><strong>Another CKEditor Plugin!</strong></p>
  40. <p>The solution is just a modified version of the CKEditor plugin in previous post. The plugin listens for pasted content, and strips out any remotely hosted Images.</p><pre class="crayon-plain-tag">CKEDITOR.plugins.add('blockpasteimagelink', {
  42. init : function(editor) {
  44. function replaceImgText(html) {
  46. var replacedSomething = false;
  48. var ret = html.replace(/&lt;img[^&gt;]*src="http.*?"[^&gt;]*&gt;/gi, function(img) {
  49. replacedSomething = true;
  50. return '';
  51. });
  53. if (replacedSomething) {
  54. alert('The Image you have attempted to paste is hosted on a remote server and may not be visible to others. Pasting these images is not currently supported, please upload an image file using the Image button in the toolbar.');
  55. }
  57. return ret;
  58. }
  60. function chkImg() {
  62. // don't execute code if the editor is readOnly
  63. if (editor.readOnly)
  64. return;
  66. setTimeout(function() {
  67. editor.document.$.body.innerHTML = replaceImgText(editor.document.$.body.innerHTML);
  68. }, 100);
  69. }
  71. editor.on('contentDom', function() {
  72. // For Firefox
  73. editor.document.on('drop', chkImg);
  74. // For IE
  75. editor.document.getBody().on('drop', chkImg);
  76. });
  78. editor.on('paste', function(e) {
  80. var html =;
  81. if (!html) {
  82. return;
  83. }
  85. = replaceImgText(html);
  87. });
  89. }
  91. });</pre><p>Save the above javascript as a script library in your nsf, called &#8216;blockpasteimagelink&#8217;</p>
  92. <p>Make sure to include the script library on your XPage, and then tell your InputRichText to use the plugin using the extraPlugins dojoAttribute. Here is a sample XPage:</p><pre class="crayon-plain-tag">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
  93. &lt;xp:view xmlns:xp=""&gt;
  95. &lt;;
  96. &lt;xp:dominoDocument var="document1"&gt;&lt;/xp:dominoDocument&gt;
  97. &lt;/;
  99. &lt;xp:this.resources&gt;
  100. &lt;xp:script src="/blockpasteimagelink.js" clientSide="true"&gt;&lt;/xp:script&gt;
  101. &lt;/xp:this.resources&gt;
  102. &lt;xp:inputRichText id="inputRichText1" value="#{document1.body}"&gt;
  103. &lt;xp:this.dojoAttributes&gt;
  104. &lt;xp:dojoAttribute name="extraPlugins" value="blockpasteimagelink"&gt;&lt;/xp:dojoAttribute&gt;
  105. &lt;/xp:this.dojoAttributes&gt;
  106. &lt;/xp:inputRichText&gt;
  107. &lt;/xp:view&gt;</pre><p>Now let&#8217;s test it out, I would like everyone to know about the mythical Jackalope, so I will copy some info from Wikipedia!</p>
  108. <p><img class="aligncenter size-full wp-image-385" src="" alt="preventpastecopyjackalope" width="368" height="400" srcset=" 368w, 276w" sizes="(max-width: 368px) 100vw, 368px" /></p>
  109. <p>Then I will paste it into my CKEditor, where I will receive a warning&#8230;</p>
  110. <p><img class="aligncenter size-full wp-image-386" src="" alt="preventpastewarning" width="638" height="262" srcset=" 638w, 300w" sizes="(max-width: 638px) 100vw, 638px" /></p>
  111. <p>And after clicking ok, I can see that everything except the image has been pasted&#8230;</p>
  112. <p><img class="aligncenter size-full wp-image-387" src="" alt="preventpasteafter" width="514" height="269" srcset=" 514w, 300w" sizes="(max-width: 514px) 100vw, 514px" /></p>
  113. <p><strong>Summary</strong></p>
  114. <p>So we have now prevented some more cases whereby the recipient of an email will have trouble viewing images, it can be a little frustating for a user, but probably less frustrating that having to re-send an email, so I call that a win.</p>
  115. <p>I had also intended on looking into the possibility of modifying the plugin so that upon pasting a remote image, the browser would try to download that image and then upload to the XPages server to be attached as an embedded image, however I haven&#8217;t looked into that yet!</p>
  116. <p>In our system we have another CKEditor plugin which will allows users to paste image data from the clipboard, this is a nicer solution and I will cover that in the next post.</p>
  117. ]]></content:encoded>
  118. <wfw:commentRss></wfw:commentRss>
  119. <slash:comments>1</slash:comments>
  120. </item>
  121. <item>
  122. <title>Preventing Pasting of Images in CKEditor</title>
  123. <link></link>
  124. <comments></comments>
  125. <pubDate>Mon, 14 Nov 2016 00:43:29 +0000</pubDate>
  126. <dc:creator><![CDATA[camerongregor]]></dc:creator>
  127. <category><![CDATA[Software Development]]></category>
  128. <category><![CDATA[ckeditor]]></category>
  129. <category><![CDATA[webmail]]></category>
  130. <category><![CDATA[xpages]]></category>
  132. <guid isPermaLink="false"></guid>
  133. <description><![CDATA[In the process of developing our XPages &#8216;Webmail&#8217; interface, we discovered that many recipients were unable to view embedded images in the emails. After investigating, it was caused by the images being embedded using&#46;&#46;&#46;]]></description>
  134. <content:encoded><![CDATA[<p>In the process of developing our XPages &#8216;Webmail&#8217; interface, we discovered that many recipients were unable to view embedded images in the emails.</p>
  135. <p>After investigating, it was caused by the images being embedded using Data URIs. <a href="">Support for Data URI Images is not universal</a>, and because it is supported in IBM Notes, everything looked like it was working ok, but a quick test viewing an email in Gmail confirmed a problem when images could not be seen.</p>
  136. <p><strong>What is a Data URI?</strong></p>
  137. <p>You are most likely familar with an image being specified by a URL to Resource, in this cause all the binary data for the image is requested from some location</p><pre class="crayon-plain-tag">&lt;img src="reddot.png" alt="Red dot" /&gt;</pre><p>With a data URI, all the binary data for the image is contained directly in the src=&#8221;&#8221; attribute, and does not need to be requested from anywhere, here is an example <a href="">I lifted from wikipedia</a>:</p><pre class="crayon-plain-tag">&lt;img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA
  139. 9TXL0Y4OHwAAAABJRU5ErkJggg==" alt="Red dot" /&gt;</pre><p>When sending emails however, it is a much better idea to embed the image as a separate mime entity, and refer to it using a &#8216;Content Identifier&#8217; (cid).</p><pre class="crayon-plain-tag">&lt;img alt="" src=cid:reddot_1479074021464&gt;&lt;/img&gt;</pre><p>The image data is then retrieved from the related mime entity which specifies that Content-ID as a MimeHeader</p><pre class="crayon-plain-tag">--==IFJRGLKFGIR32748UHRUHIHD
  140. Content-Type: image/jpeg
  141. Content-Disposition: inline;
  142. filename="reddot_1479074021464.JPG"
  143. Content-ID: &lt;reddot_1479074021464&gt;
  144. Content-Transfer-Encoding: base64
  147. ...more binary data...
  148. iigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/9k=
  151. --==IFJRGLKFGIR32748UHRUHIHD--</pre><p><strong>Sending Embedded Images in Emails<br />
  152. </strong></p>
  153. <p>The best format for sending emails is the &#8216;content-id&#8217; method, and the Xpages CKEditor provides a toolbar button which will perform the image upload and attach as  separate mime entity.</p>
  154. <p>On the other hand, an easy way for users to insert images is to use some screen capture software such as Snipping Tool and then copy and paste into the CKEditor. When this method is used, the image is inserted as a Data URI.</p>
  155. <p>By default, some browsers don&#8217;t support the pasting of images anyway, but Firefox and probably some others do, and it must be stopped!</p>
  156. <p><strong>Preventing Pasting of Images using a CKEditor plugin</strong></p>
  157. <p>After a bit of googling I found that <a href="">someone else had already come up with a plugin which prevents pasted images,</a> and it worked without modification for XPages</p>
  158. <p>I will repost the plugin here, the only modification I have made is the alert message to explain to the User to use the image upload toolbar button</p><pre class="crayon-plain-tag">CKEDITOR.plugins.add('blockimagepaste', {
  160. init : function(editor) {
  162. function replaceImgText(html) {
  164. var ret = html.replace(/&lt;img[^&gt;]*src="data:image\/(bmp|dds|gif|jpg|jpeg|png|psd|pspimage|tga|thm|tif|tiff|yuv|ai|eps|ps|svg);base64,.*?"[^&gt;]*&gt;/gi, function(img) {
  165. alert("Pasting Image Data is not allowed. Please use the Image Upload button (if available).");
  166. return '';
  167. });
  169. return ret;
  170. }
  172. function chkImg() {
  173. // don't execute code if the editor is readOnly
  174. if (editor.readOnly)
  175. return;
  177. setTimeout(function() {
  178. editor.document.$.body.innerHTML = replaceImgText(editor.document.$.body.innerHTML);
  179. }, 100);
  180. }
  182. editor.on('contentDom', function() {
  183. // For Firefox
  184. editor.document.on('drop', chkImg);
  185. // For IE
  186. editor.document.getBody().on('drop', chkImg);
  187. });
  189. editor.on('paste', function(e) {
  191. var html =;
  192. if (!html) {
  193. return;
  194. }
  196. = replaceImgText(html);
  197. });
  199. } // Init
  200. });</pre><p>To use the plugin, copy the above code into a Client Side JavaScript Library &#8216;blockimagepaste&#8217;.</p>
  201. <p><img class="aligncenter size-full wp-image-378" src="" alt="preventpastescriptlib" width="699" height="309" srcset=" 699w, 300w" sizes="(max-width: 699px) 100vw, 699px" /></p>
  202. <p>&nbsp;</p>
  203. <p>Then, make sure to include the ScriptLibrary as a resource on your page. Also add the dojoAttribute &#8216;extraPlugins&#8217; with the name of the plugin (from line 1 above &#8216;blockimagepaste&#8217;.</p><pre class="crayon-plain-tag">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
  204. &lt;xp:view xmlns:xp=""&gt;
  206. &lt;;
  207. &lt;xp:dominoDocument var="document1"&gt;&lt;/xp:dominoDocument&gt;
  208. &lt;/;
  210. &lt;xp:this.resources&gt;
  211. &lt;xp:script src="/blockimagepaste.js" clientSide="true"&gt;&lt;/xp:script&gt;
  212. &lt;/xp:this.resources&gt;
  213. &lt;xp:inputRichText id="inputRichText1" value="#{document1.body}"&gt;
  214. &lt;xp:this.dojoAttributes&gt;
  215. &lt;xp:dojoAttribute name="extraPlugins" value="blockimagepaste"&gt;&lt;/xp:dojoAttribute&gt;
  216. &lt;/xp:this.dojoAttributes&gt;
  217. &lt;/xp:inputRichText&gt;
  218. &lt;/xp:view&gt;</pre><p>So now when a user attempts to paste an image it will not be completed, and they will be given the alert</p>
  219. <p><img class="aligncenter size-full wp-image-376" src="" alt="preventpastealert" width="734" height="309" srcset=" 734w, 300w" sizes="(max-width: 734px) 100vw, 734px" /></p>
  220. <p><strong>Summary and Next Steps</strong></p>
  221. <p>So in this post we added a CKEditor plugin to our application, and configured our InputRichText to use it.</p>
  222. <p>The plugin prevents the pasting of data URI images, however you could modify this to prevent any html you like, in a future post we will show a modified version of this plugin to prevent images pasted using URLs. This can be troublesome for emails, as users may copy and paste html from internal applications, which is then not available to external email recipients.</p>
  223. <p>Also, copying and pasting images is actually quite a useful feature don&#8217;t you think? our users did too, so we also implemented another plugin which intercepts the pasted image, and uploads it using the normal &#8216;Content-ID&#8217; method instead, I will share that solution as well in a future post.</p>
  224. <p>&nbsp;</p>
  225. ]]></content:encoded>
  226. <wfw:commentRss></wfw:commentRss>
  227. <slash:comments>2</slash:comments>
  228. </item>
  229. <item>
  230. <title>Controlling the order of Script Resources (e.g. Jquery) with a Custom ViewRootRenderer</title>
  231. <link></link>
  232. <comments></comments>
  233. <pubDate>Mon, 19 Sep 2016 13:25:19 +0000</pubDate>
  234. <dc:creator><![CDATA[camerongregor]]></dc:creator>
  235. <category><![CDATA[Software Development]]></category>
  236. <category><![CDATA[renderer]]></category>
  237. <category><![CDATA[xpages]]></category>
  239. <guid isPermaLink="false"></guid>
  240. <description><![CDATA[When loading Client Side Javascript libraries in XPages, sometimes the order that the libraries are &#8216;encoded&#8217; (or written in HTML) in the &#60;head&#62; tag is important. For example jQuery and some of it&#8217;s plugins can have some issues&#46;&#46;&#46;]]></description>
  241. <content:encoded><![CDATA[<p>When loading Client Side Javascript libraries in XPages, sometimes the order that the libraries are &#8216;encoded&#8217; (or written in HTML) in the &lt;head&gt; tag is important.</p>
  242. <p>For example jQuery and some of it&#8217;s plugins can have some issues if Dojo is encoded first.</p>
  243. <p>By default in XPages you don&#8217;t have too much say in what is written out first, a nifty workaround for this has been shared by Sven Hasselbach (<a href="">here</a> and <a href="">here</a>) which utilises the lesser known &lt;xp:headTag&gt; tag. This workaround ensures the specified libraries will be encoded before all the normal XPages resources are encoded.</p>
  244. <blockquote><p>UPDATE!! It turns out that Sven <a href="">made a solution like this</a> a couple of years after those original posts! I just didn&#8217;t see it because all my googling took me to the original posts. So check out his solution as well as it allows a bit more control.</p></blockquote>
  245. <p>There are only 2 downsides to this method and they are not too major.</p>
  246. <ol>
  247. <li>You must have resource aggregation turned on</li>
  248. <li>You can&#8217;t use a theme to specify the resources</li>
  249. </ol>
  250. <p>I wanted to come up with another solution because sometimes I like to turn resource aggregation off while developing, and also I would prefer to specify these resources in a theme instead of on individual Xpages, or alternatively adding the resources programatically with java.</p>
  251. <p><strong>Developing the solution</strong></p>
  252. <p>In the end, the actual solution is not too complicated. But, getting to that point is always the tricky part so I will explain how I went about it, and at the end you can install to your own applications and modify if you like.</p>
  253. <p>The starting point is to ask, &#8220;Well how does XPages actually write out these &lt;script&gt; / &lt;link&gt; tags anyway?&#8221;. The answer is that it does so using a Renderer.</p>
  254. <p>At the very Root of your XPages component tree is the UIViewRoot (corresponding to the &lt;xp:view&gt; tag.</p>
  255. <p>Just like all your other components on your xpage (e.g. panel, inputText, viewPanel etc.) have renderers, so too does the UIViewRoot component, and it is this renderer that is responsible for generating the surrounding &lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt; tags.</p>
  256. <p>We don&#8217;t want to re invent the wheel, we just want to change it&#8217;s behaviour ever so slightly, so our plan is to extend the existing view root renderer, override one of it&#8217;s methods and register our new renderer in place of the other one.</p>
  257. <p><strong>Creating our ViewRootRenderer</strong></p>
  258. <p>The default View Renderer for XPages is of the class <em> </em>So we are going to extend that one and see what we can do with it.</p>
  259. <p>I create new Java class in the NSF</p><pre class="crayon-plain-tag">package com.gregorbyte.xsp.ViewRootRenderer;
  261. import;
  263. public class ViewRootRenderer extends ViewRootRendererEx2 {
  265. }</pre><p>Now which method to override? Lets have at the ones we can and see which might be it.</p>
  266. <p>We can use the Source =&gt; Override/Implement Methods dialog to show us some candidates.</p>
  267. <p><img class="aligncenter size-full wp-image-357" src="" alt="viewrootoverridemethod" width="512" height="718" srcset=" 512w, 214w" sizes="(max-width: 512px) 100vw, 512px" /></p>
  268. <p>Encode Resources List, sounds pretty good. All Stylesheets, scripts etc. are known as &#8216;Resources&#8217; so this should be what we are looking for. Let&#8217;s override that method and throw in some testing code to see if we are on the right track.</p><pre class="crayon-plain-tag">package com.gregorbyte.xsp.ViewRootRenderer;
  270. import;
  271. import java.util.List;
  273. import javax.faces.context.FacesContext;
  274. import javax.faces.context.ResponseWriter;
  276. import;
  277. import;
  278. import;
  280. public class ViewRootRenderer extends ViewRootRendererEx2 {
  282. @Override
  283. protected void encodeResourcesList(FacesContext context, UIViewRootEx viewRoot,
  284. ResponseWriter writer, List&lt;Resource&gt; resources) throws IOException {
  286. // Write a comment so we can see in the generated HTML we are in the right spot
  287. writer.writeComment("Before Resources");
  288. writer.write("\n"); // new line
  289. super.encodeResourcesList(context, viewRoot, writer, resources);
  291. // Write a comment so we can see in the generated HTML we are in the right spot
  292. writer.writeComment("After Resources");
  293. writer.write("\n"); // new line
  294. }
  296. }</pre><p>Next thing to do is register our Renderer, and then tell XPages to Use it for our view root.</p>
  297. <p><strong>Register the Renderer</strong></p>
  298. <p>To do this we need to tell XPages (via faces config)</p>
  299. <ul>
  300. <li>The Component family that our renderer is available to</li>
  301. <li>What name do we refer to our renderer (renderer-type)</li>
  302. <li>What is the Java Class of this renderer</li>
  303. </ul>
  304. <p>The component family can be found with a <a href="">little debugging</a>, we can choose whatever name we want for renderer-type, and we know the java class because we just created it!</p>
  305. <p>our faces config entry is like so:</p><pre class="crayon-plain-tag">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
  306. &lt;faces-config&gt;
  307.  &lt;!--AUTOGEN-START-BUILDER: Automatically generated by IBM Domino Designer. Do not modify.--&gt;
  308.  &lt;!--AUTOGEN-END-BUILDER: End of automatically generated section--&gt;
  310.   &lt;render-kit&gt;
  311.   &lt;renderer&gt;
  312.   &lt;component-family&gt;javax.faces.ViewRoot&lt;/component-family&gt;
  313.   &lt;renderer-type&gt;com.gregorbyte.xsp.ViewRoot&lt;/renderer-type&gt;
  314.   &lt;renderer-class&gt;com.gregorbyte.xsp.ViewRootRenderer.ViewRootRenderer&lt;/renderer-class&gt;
  315.   &lt;/renderer&gt;
  316.   &lt;/render-kit&gt;
  318. &lt;/faces-config&gt;</pre><p><strong>Tell Xpages to use our renderer</strong></p>
  319. <p>Here we have 3 options!</p>
  320. <ol>
  321. <li>Specify on an XPage by XPage basis, some XPages use ours some don&#8217;t</li>
  322. <li>Use a theme to set the ViewRootRenderer for every page in the application</li>
  323. <li>Override the default renderer directly in faces-config</li>
  324. </ol>
  325. <p><strong>Option 1: Specify on a Page by Page basis</strong></p>
  326. <p>To do this we find the root &lt;xp:view&gt; tag of our xpage, and set the rendererType attribute there</p><pre class="crayon-plain-tag">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
  327. &lt;xp:view xmlns:xp="" rendererType="com.gregorbyte.xsp.ViewRoot"&gt;
  330. &lt;/xp:view&gt;</pre><p>Although this works, it is the least desirable option, but it is a good technique if you quickly want to change a renderer of a single control.<br />
  331. Since the whole point of this solution is to do it once and for all let&#8217;s move on to the next option instead.</p>
  332. <p><strong>Option 2: Use a Theme to set the ViewRootRenderer for every XPage</strong></p>
  333. <p>This is a better option, in the theme we are basically saying to xpages, whenever you find a &lt;xp:view&gt; set the rendererType property to our rendererType (just as we did manually above).</p>
  334. <p>Below is the entry to put in the Theme, and be sure to have your theme selected for your application.</p><pre class="crayon-plain-tag">&lt;theme extends="webstandard" xmlns:xsi="" xsi:noNamespaceSchemaLocation="platform:/plugin/"&gt;
  335. &lt;control&gt;
  336. &lt;name&gt;ViewRoot&lt;/name&gt;
  337. &lt;property&gt;
  338. &lt;name&gt;rendererType&lt;/name&gt;
  339. &lt;value&gt;com.gregorbyte.xsp.ViewRoot&lt;/value&gt;
  340. &lt;/property&gt;
  341. &lt;/control&gt;
  342. &lt;/theme&gt;</pre><p><strong>Option 3: Override the default renderer</strong></p>
  343. <p>This is another way, where you &#8216;hijack&#8217; the rendererType that already exists ( This way whenever XPages tries to find the IBM renderer, it will find ours instead.</p>
  344. <p>To do this we would specify IBM&#8217;s renderer type instead of the one that we created.</p><pre class="crayon-plain-tag">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
  345. &lt;faces-config&gt;
  346.  &lt;render-kit&gt;
  347.    &lt;renderer&gt;
  348.      &lt;component-family&gt;javax.faces.ViewRoot&lt;/component-family&gt;
  349. &lt;!--      &lt;renderer-type&gt;com.gregorbyte.xsp.ViewRoot&lt;/renderer-type&gt;--&gt;
  350.      &lt;renderer-type&gt;;/renderer-type&gt;
  351.      &lt;renderer-class&gt;com.gregorbyte.xsp.ViewRootRenderer.ViewRootRenderer&lt;/renderer-class&gt;
  352.    &lt;/renderer&gt;
  353.  &lt;/render-kit&gt;
  354.  &lt;!--AUTOGEN-START-BUILDER: Automatically generated by IBM Domino Designer. Do not modify.--&gt;
  355.  &lt;!--AUTOGEN-END-BUILDER: End of automatically generated section--&gt;
  356. &lt;/faces-config&gt;</pre><p>Which method to use is up to you but I prefer option 2 as the intention is clearest.</p>
  357. <p><strong>Check that our renderer is being used</strong></p>
  358. <p>So to check we are using our renderer we would expect to see the comments that we writing in our encodeResourceList method to appear in the generated HTML. Lets do that now by using the view-source function in our browser.</p>
  359. <p><img class="aligncenter size-full wp-image-358" src="" alt="viewrootcommentsarethere" width="702" height="366" srcset=" 702w, 300w" sizes="(max-width: 702px) 100vw, 702px" /></p>
  360. <p>Great! So this means we are able to control what is written out at these spots.</p>
  361. <p><strong>What to do next?</strong></p>
  362. <p>So we know we can write whatever we want at these points in the &lt;head&gt; tag. We could go the cheap and nasty route and just write out the script tags directly from our renderer:</p><pre class="crayon-plain-tag">@Override
  363. protected void encodeResourcesList(FacesContext context, UIViewRootEx viewRoot,
  364. ResponseWriter writer, List&lt;Resource&gt; resources) throws IOException {
  366. // Write a comment so we can see in the generated HTML we are in the right spot
  367. writer.writeComment("Before Resources");
  368. writer.write("\n"); // new line
  369. writer.startElement("script", null);
  370. writer.writeAttribute("type", "text/javascript", null);
  371. writer.writeAttribute("src", "js/itsfiveoclock.js", null);
  372. writer.endElement("script");
  373.                writer.write("\n");
  374. // Write a comment so we can see in the generated HTML we are in the right spot
  375. writer.writeComment("Start Normal Resources");
  376. writer.write("\n"); // new line
  377. super.encodeResourcesList(context, viewRoot, writer, resources);
  379. // Write a comment so we can see in the generated HTML we are in the right spot
  380. writer.writeComment("After Resources");
  381. writer.write("\n"); // new line
  382. }</pre><p>and this does the job, but it is not a super flexible solution as it dose not allow for customisation on a XPage by XPage basis, i.e. all XPages will have the same.</p>
  383. <p><img class="aligncenter size-full wp-image-359" src="" alt="viewrootquickndirty" width="507" height="235" srcset=" 507w, 300w" sizes="(max-width: 507px) 100vw, 507px" /></p>
  384. <p>So we need some sort of test to identify:</p>
  385. <ul>
  386. <li>which resources should be at the top</li>
  387. <li>which should be at the bottom</li>
  388. <li>which should be left to normal (and free to participate in resource aggregation if they like!)</li>
  389. </ul>
  390. <p>Firstly I will try to make 2 resources that are specified on an XPage to appear in the &#8216;before&#8217; and &#8216;after&#8217; sections, and then we will do one from a theme.</p>
  391. <p>Because the ScriptResource implements the FacesAttrObject interface, it can have child &#8216;attr&#8217; tags that you can specify to be whatever you want. Other resources like the StyleSheetResource also can do this too.<br />
  392. I will utilise this and specify my own &#8216;encode-position&#8217; attribute that I can use later in the renderer.</p><pre class="crayon-plain-tag">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
  393. &lt;xp:view xmlns:xp=""&gt;
  395. &lt;xp:this.resources&gt;
  396. &lt;xp:script src="js/loadedfrompagebefore.js" clientSide="true"&gt;
  397. &lt;xp:this.attrs&gt;
  398. &lt;xp:attr name="encode-position" value="before"&gt;&lt;/xp:attr&gt;
  399. &lt;/xp:this.attrs&gt;
  400. &lt;/xp:script&gt;
  401. &lt;xp:script src="js/loadedfrompageafter.js" clientSide="true"&gt;
  402. &lt;xp:this.attrs&gt;
  403. &lt;xp:attr name="encode-position" value="after"&gt;&lt;/xp:attr&gt;
  404. &lt;/xp:this.attrs&gt;
  405. &lt;/xp:script&gt;
  407. &lt;/xp:this.resources&gt;
  409. &lt;/xp:view&gt;</pre><p>Now in the Renderer we add a bit more logic.</p>
  410. <p>We make a method that will inspect a resource to determine if it should be before, after or normal, based on the encode-position attribute.</p><pre class="crayon-plain-tag">/*
  411. * This method will inspect a Resource and return whether it should be before
  412. * after or normal (null)
  413. */
  414. private String getPosition(Resource resource) {
  416. // Your logic for choosing position goes here!
  418. // This test is to see if the resource has a child 'attr' tag which
  419. // specifies the encode-position of before or aftre
  420. if (resource instanceof FacesAttrsObject) {
  422. FacesAttrsObject o = (FacesAttrsObject) resource;
  424. if (o.getAttrs() != null) {
  426. for (Attr attr : o.getAttrs()) {
  428. if (StringUtil.equals(attr.getName(), "encode-position")) {
  429. return attr.getValue();
  430. }
  431. }
  433. }
  435. }
  437. // None of our tests matched so we just return null
  438. return null;
  440. }</pre><p>And then in our encodeResourceLists, we process the page resources into 3 lists, the ones to render before, the ones to render normally (possibly aggregated if that is set) and the ones to render after.</p><pre class="crayon-plain-tag">@Override
  441. protected void encodeResourcesList(FacesContext context,
  442. UIViewRootEx viewRoot, ResponseWriter writer,
  443. List&lt;Resource&gt; resources) throws IOException {
  445. // Create 3 Lists of Resources
  446. List&lt;Resource&gt; before = new ArrayList&lt;Resource&gt;();
  447. List&lt;Resource&gt; normal = new ArrayList&lt;Resource&gt;();
  448. List&lt;Resource&gt; after = new ArrayList&lt;Resource&gt;();
  450. // Process the resources into our new lists
  451. for (Resource resource : resources) {
  453. String position = getPosition(resource);
  455. if (StringUtil.equals(position, "before")) {
  456. before.add(resource);
  457. } else if (StringUtil.equals(position, "after")) {
  458. after.add(resource);
  459. } else {
  460. normal.add(resource);
  461. }
  463. }
  465. // Write a comment so we can see in the generated HTML we are in the
  466. // right spot
  467. writer.writeComment("Before Resources");
  468. writer.write("\n"); // new line
  470. // Encode Resources manually, note we don't pass them to the super
  471. // method as we don't want them to be aggregated
  472. for (Resource resource : before) {
  473. encodeResource(context, viewRoot, writer, resource);
  474. }
  476. // Write a comment so we can see in the generated HTML we are in the
  477. // right spot
  478. writer.writeComment("Start Normal Resources");
  479. writer.write("\n"); // new line
  480. super.encodeResourcesList(context, viewRoot, writer, normal);
  482. // Write a comment so we can see in the generated HTML we are in the
  483. // right spot
  484. writer.writeComment("After Resources");
  485. writer.write("\n"); // new line
  487. // Encode Resources manually, note we don't pass them to the super
  488. // method as we don't want them to be aggregated
  489. for (Resource resource : before) {
  490. encodeResource(context, viewRoot, writer, resource);
  491. }
  493. }</pre><p>Ok, lets check out html and see if it worked&#8230;</p>
  494. <p><img class="aligncenter size-full wp-image-361" src="" alt="viewrootpagescripts" width="662" height="272" srcset=" 662w, 300w" sizes="(max-width: 662px) 100vw, 662px" /></p>
  495. <p>Woohoo, so that&#8217;s the good news, the bad news is this won&#8217;t work from a theme, because we can&#8217;t utilise the attr&#8217;s functionality from theme. We will have to think of something else.</p>
  496. <p>My solution is a bit of a hack in itself, but I am going to temporarily hijack the &#8216;type&#8217; property of a script resource with some extra information, so instead of &#8216;type/javascript&#8217; I will put &#8216;type/javascript/before&#8217; or &#8216;type/javascript/after&#8217;.</p>
  497. <p>Then in the renderer, I will check for this, and then fix it up (restore it back to &#8216;type/javascript&#8217; so it will be correct when renderered) and attach the &#8216;Attr&#8217; at that point so that any subsequent tests will use the attr instead to determine before or after.</p><pre class="crayon-plain-tag">/*
  498. * This method will inspect a Resource and return whether it should be on
  499. * top or bottom or normal (null)
  500. */
  501. private String getPosition(Resource resource) {
  503. // Your logic for choosing position goes here!
  505. // This test is to see if the resource has a child 'attr' tag which
  506. // specifies the encode-position of before or aftre
  507. if (resource instanceof FacesAttrsObject) {
  509. FacesAttrsObject o = (FacesAttrsObject) resource;
  511. if (o.getAttrs() != null) {
  513. for (Attr attr : o.getAttrs()) {
  515. if (StringUtil.equals(attr.getName(), "encode-position")) {
  516. return attr.getValue();
  517. }
  518. }
  520. }
  522. }
  523. if (resource instanceof ScriptResource) {
  525. // Cast it to be a ScriptResource so we can access it as such
  526. ScriptResource sr = (ScriptResource) resource;
  528. String type = sr.getType();
  530. if (StringUtil.isNotEmpty(type)) {
  532. if (type.endsWith("/before")) {
  534. // Fix Up the Type
  535. sr.setType(type.replace("/before", ""));
  537. // Add an attribute instead so that next time it is check it
  538. // will use that method
  539. sr.addAttr(new Attr("encode-position", "before"));
  541. return "before";
  542. } else if (type.endsWith("/after")) {
  544. // Fix Up the Type
  545. sr.setType(type.replace("/after", ""));
  547. // Add an attribute instead so that next time it is check it
  548. // will use that method
  549. sr.addAttr(new Attr("encode-position", "after"));
  551. return "after";
  553. }
  555. }
  557. } else if (resource instanceof StyleSheetResource) {
  559. StyleSheetResource ssr = (StyleSheetResource) resource;
  560. // You could do the same type of thing here for stylesheet
  562. } else if (resource instanceof LinkResource) {
  564. LinkResource lr = (LinkResource) resource;
  565. // You could do the same type of thing here for Link Resource
  566. //maybe even use something like title or Styleclass instead
  567. //lr.getStyleClass();
  568. //lr.getTitle();
  570. }
  572. // None of our tests matched so we just return null
  573. return null;
  575. }</pre><p>Lets add some scripts to our theme with this new convention of added /after or /before</p><pre class="crayon-plain-tag">&lt;theme extends="webstandard" xmlns:xsi="" xsi:noNamespaceSchemaLocation="platform:/plugin/"&gt;
  576. &lt;resources&gt;
  577. &lt;script src="js/loadfromthemebefore.js" clientSide="true" type="text/javascript/before"/&gt;
  578. &lt;script src="js/loadfromthemeafter.js" clientSide="true" type="text/javascript/after"/&gt;
  579. &lt;/resources&gt;
  580. &lt;control&gt;
  581. &lt;name&gt;ViewRoot&lt;/name&gt;
  582. &lt;property&gt;
  583. &lt;name&gt;rendererType&lt;/name&gt;
  584. &lt;value&gt;com.gregorbyte.xsp.ViewRoot&lt;/value&gt;
  585. &lt;/property&gt;
  586. &lt;/control&gt;
  587. &lt;/theme&gt;</pre><p>and check our html to see if it worked!</p>
  588. <p><img class="aligncenter size-full wp-image-362" src="" alt="viewrootwiththemes" width="678" height="375" srcset=" 678w, 300w" sizes="(max-width: 678px) 100vw, 678px" /></p>
  589. <p><strong>Conclusion</strong></p>
  590. <p>I hope this has shown how you can extend and modify the behaviour of XPages. In this case to solve a common problem that I&#8217;m sure many have faced.</p>
  591. <p>I have put these example files in a <a href="">github repository</a>, let me know if any questions / problems!</p>
  592. <p>Even if you don&#8217;t have a need this script before /after business, I hope you got something out of this anyway!</p>
  593. ]]></content:encoded>
  594. <wfw:commentRss></wfw:commentRss>
  595. <slash:comments>2</slash:comments>
  596. </item>
  597. <item>
  598. <title>Review: JRebel with Domino OSGi development</title>
  599. <link></link>
  600. <comments></comments>
  601. <pubDate>Tue, 06 Sep 2016 01:58:33 +0000</pubDate>
  602. <dc:creator><![CDATA[camerongregor]]></dc:creator>
  603. <category><![CDATA[Software Development]]></category>
  604. <category><![CDATA[domino]]></category>
  605. <category><![CDATA[jrebel]]></category>
  606. <category><![CDATA[plugin]]></category>
  607. <category><![CDATA[xpages]]></category>
  609. <guid isPermaLink="false"></guid>
  610. <description><![CDATA[Last year I finally figured out how to use JRebel with Domino, and I posted a how-to video. I thought I would do a quick follow up to say how it&#8217;s going. It is&#46;&#46;&#46;]]></description>
  611. <content:encoded><![CDATA[<p>Last year I finally figured out how to use JRebel with Domino, and I posted a <a href="">how-to video</a>. I thought I would do a quick follow up to say how it&#8217;s going.</p>
  612. <p>It is going great! I can&#8217;t imagine giving back my JRebel license. I have gone entire days without restarting my http server.</p>
  613. <p>If you are only developing xpages from within an NSF, and don&#8217;t do any OSGi plugin development, then you really don&#8217;t have much need for JRebel. But if you are involved in any OSGi Library development and in particular UI Component development, then I would recommended fighting tooth and nail to get yourself a JRebel licence.</p>
  614. <p>I have done a bunch of UIComponent development in the last few months and I would have to say, that it would have probably just given up on it all if I didn&#8217;t have JRebel, as there is lots of small changes that would be so painful if it required me to restart all the time, especially when it is just a typo or an incorrectly sized array!</p>
  615. <p><strong>A couple of points</strong></p>
  616. <p><span style="text-decoration: underline;">Static instances and inner classes</span></p>
  617. <p>There are some things that JRebel can&#8217;t reload on the fly, thankfully it is only occasionally that you come across this, JRebel will say something like &#8220;Couldn&#8217;t load &lt;blah blah&gt; because something about static things&#8221;  in the domino console.</p>
  618. <p><span style="text-decoration: underline;">Java Security Settings</span></p>
  619. <p>When trying to do certain things JRebel can causes a AccessControlException error due to the java security settings. You can get around this by putting your **development** server into &#8216;AllPermissions&#8217; mode.</p>
  620. <p>e.g. putting this entry in a java.pol file in the domino/jvm/lib/security/ directory</p><pre class="crayon-plain-tag">grant {
  621.    permission;
  622. }</pre><p>This is not a perfect situation as it probably won&#8217;t mirror the settings on your Production server, and it might mask another AccessControlException that could be triggered by some other part of the code you are running.</p>
  623. <p>Theoretically it should be possible to write a Java Security entry that specifically covers JRebel but on both occassions that I tried this I failed so I decided just to put it the dev server in &#8216;all permissions&#8217; mode and then turn this off when I want to test anything special that could cause AccessControlException&#8217;s e.g. reflection / use of 3rd party libraries.</p>
  624. <p><strong>Video Correction / Clarification</strong></p>
  625. <p>In the how-to video, I mention that I don&#8217;t think the REBEL.base config is that important. However it is important.<br />
  626. When you start your Domino Server, the Rebel Base directory will be different, depending on whether you start your server as a regular application or Start it as a service.</p>
  627. <p>You don&#8217;t have to specify the rebel.base location, but if you leave it blank, just know that JRebel will determine this directory based on whatever user account launches the server.</p>
  628. <p>So if you launch as a service, it will use the HOME directory of whatever user is set to launch the service (perhaps Administrator?), or if you start as application, it will use your own HOME directory.</p>
  629. <p>&nbsp;</p>
  630. ]]></content:encoded>
  631. <wfw:commentRss></wfw:commentRss>
  632. <slash:comments>2</slash:comments>
  633. </item>
  634. <item>
  635. <title>Extended Messages Control available as an XspLibrary</title>
  636. <link></link>
  637. <comments></comments>
  638. <pubDate>Tue, 12 Jul 2016 11:49:00 +0000</pubDate>
  639. <dc:creator><![CDATA[camerongregor]]></dc:creator>
  640. <category><![CDATA[Software Development]]></category>
  641. <category><![CDATA[control]]></category>
  642. <category><![CDATA[messages]]></category>
  643. <category><![CDATA[plugin]]></category>
  644. <category><![CDATA[xpages]]></category>
  646. <guid isPermaLink="false"></guid>
  647. <description><![CDATA[A while ago I shared an Extended version of the messages control which allows for multiple messages to be displayed for a single control at the same time. Originally this was just shared as a&#46;&#46;&#46;]]></description>
  648. <content:encoded><![CDATA[<p>A while ago I shared an <a href="" target="_blank">Extended version of the messages control</a> which allows for multiple messages to be displayed for a single control at the same time.</p>
  649. <p>Originally this was just shared as a &#8216;control within an NSF&#8217;.</p>
  650. <p>One of the problems with the control within an NSF approach is that you repeatedly get an &#8216;unknown tag&#8217; compilation problem which eventually goes away after a &#8216;project clean&#8217; but it is very annoying nonetheless.</p>
  651. <p>I receiving a request to package the control within a deployable XspLibrary. So I have update the Github Project and created a release with the plugin built and ready to deploy.</p>
  652. <p>You can find the release here:</p>
  653. <p><a href=""></a></p>
  654. <p>Let me know if you have any problems!</p>
  655. ]]></content:encoded>
  656. <wfw:commentRss></wfw:commentRss>
  657. <slash:comments>0</slash:comments>
  658. </item>
  659. <item>
  660. <title>Webmail UI &#8211; You must learn about MIME</title>
  661. <link></link>
  662. <comments></comments>
  663. <pubDate>Wed, 20 Apr 2016 14:14:36 +0000</pubDate>
  664. <dc:creator><![CDATA[camerongregor]]></dc:creator>
  665. <category><![CDATA[Software Development]]></category>
  666. <category><![CDATA[mime]]></category>
  667. <category><![CDATA[webmail]]></category>
  668. <category><![CDATA[xpages]]></category>
  670. <guid isPermaLink="false"></guid>
  671. <description><![CDATA[If you were like me, you spent many years developing classic Notes applications before making the switch to XPages. If this was the case, you were no doubt comfortable with the notion of a&#46;&#46;&#46;]]></description>
  672. <content:encoded><![CDATA[<p>If you were like me, you spent many years developing classic Notes applications before making the switch to XPages. If this was the case, you were no doubt comfortable with the notion of a RichText field.<br />
  673. You probably even occasionally did some RichText manipulation in LotusScript, adding Paragraphs and formatting using RichTextStyles and <a href="" target="_blank">RichTextNavigators</a>, attaching Files using EmbeddedObjects.</p>
  674. <p>And then XPages comes along, and says &#8220;If you want to edit any RichText through XPages, it is going to be saved as MIME&#8221;.  And I&#8217;m like &#8220;Ok whatever you say, although, I don&#8217;t know what your&#8217;e talking about and I&#8217;m too busy trying to learn all these other new things to also learn about MIME&#8221;</p>
  675. <p>And then later on, you come along and write an agent or a function that is meant to deal with a RichText Item and you don&#8217;t know why it doesn&#8217;t work and why everything looks funny after you do it, and it all comes down to the difference between RichText and MIME but you didn&#8217;t even realise there was a difference.</p>
  676. <p>If you can understand some core principles of this Mime / Rich Text ecosystem, then you will be on a much better footing to solve the problems which inevitably appear whenever you are getting involved with Mime / Rich Text creating and manipulation.</p>
  677. <p><strong>Confusing Terms</strong></p>
  678. <p>I think one origin of confusion is that the phrase &#8216;Rich Text&#8217; is often used to describe more than one thing.</p>
  679. <p>To demonstrate what I mean, let&#8217;s think about &#8216;Audio files&#8217;. Imagine you are a programmer and your problem is that you need to record and playback some sound. Well what file format do you use? Maybe you decide to come up with your own format like Microsoft did e.g. Windows Media Audio (.wma) or alternatively, maybe you decide to use a format that everybody else uses e.g. MPEG layer-3 (.mp3).</p>
  680. <p>The end result for both these &#8216;Audio Files&#8217; is that you hear sound coming out your speaker just the same, <span style="text-decoration: underline;">but</span> they are store in different underlying formats. Additionally, if I talk to you about the above situation and I say the word &#8216;<strong>Audio File</strong>&#8216;, you know that I am talking about the CONCEPT of an audio file, not any specific format.</p>
  681. <ul>
  682. <li>Audio Files
  683. <ul>
  684. <li>mp3</li>
  685. <li>wma</li>
  686. </ul>
  687. </li>
  688. </ul>
  689. <p>In comparison, IBM came up with there own way to save &#8216;Pretty words, pictures and files&#8217;, and the rest of the world came up with another way &#8216;MIME&#8217;, but in Notes/Domino we tend use the word RichText to describe both the CONCEPT of &#8216;Pretty words, pictures and files&#8217; and also we use RichText to describe the specific format IBM created to store these items.</p>
  690. <ul>
  691. <li>RichText (concept)
  692. <ul>
  693. <li>RichText</li>
  694. <li>MIME</li>
  695. </ul>
  696. </li>
  697. </ul>
  698. <p>So you can see the confusion here, someone says &#8220;I am storing it as RichText&#8221; well what do you mean? are you talking about the format or the concept???<br />
  699. A less confusing way to think about it is maybe:</p>
  700. <ul>
  701. <li>Pretty words, pictures and files
  702. <ul>
  703. <li>RichText format</li>
  704. <li>MIME format</li>
  705. </ul>
  706. </li>
  707. </ul>
  708. <p><strong>Learning about MIME</strong></p>
  709. <div>If you don&#8217;t know about <a href="" target="_blank">MIME</a>, this is one of the first things you should try to get an understanding of because you will need to know what&#8217;s going on. MIME stands for <em>Multipart Internet Mail Extensions, </em></div>
  710. <div>
  711. <p>When they first invented email, <a href="" target="_blank">RFC822 </a>was the standard which defined how a computer could send a text message to another. This was great, but then people wanted to send messages with using different character sets, they wanted to include images and attachments (Look <a href="" data-rel="lightbox-video-0" target="_blank">how excited Steve Ballmer is</a> about putting a picture of a Ferrari in a &#8216;Microsoft Write&#8217; document). So MIME was invented, which was a new standard of transferring this more complicated information over the same RFC822 compliant system.</p>
  712. <p>In a crude nutshell, a MIME message is just a big long text string/file, which is formatted in a precise way in accordance to the MIME standard.</p>
  713. <p>The Wikipedia article on MIME is a good overview, but if you are ever in doubt about exactly how it works, read the RFC&#8217;s that are linked there as these are from the horse&#8217;s mouth and they form the actual standard that software would hopefully adhere to.</p>
  714. <p><strong>Inspecting some MIME</strong></p>
  715. <p>Let&#8217;s have a quick look at a MIME message. Many email clients / webmail clients will have some way for you to inspect the original &#8216;MIME&#8217; version of your email. To demonstrate, I will send myself an Email in Gmail and have a look.</p>
  716. <p>Here is a draft Email that I am sending myself, it has an embedded Image, and it also has an Attachment.</p>
  717. <p><img class="aligncenter size-full wp-image-300" src="" alt="MimeDraftEmail" width="595" height="601" srcset=" 595w, 297w" sizes="(max-width: 595px) 100vw, 595px" /></p>
  718. <p>So now I send the email to myself, and when I open it in Gmail, I can choose option to &#8216;Show Original&#8217;.</p>
  719. <p><img class="aligncenter size-full wp-image-301" src="" alt="MimeShowOriginal" width="792" height="459" srcset=" 792w, 300w, 768w" sizes="(max-width: 792px) 100vw, 792px" /></p>
  720. <p>This then opens up the Original MIME representation that was received for Gmail</p>
  721. <p><img class="aligncenter size-full wp-image-303" src="" alt="MimeMessage" width="526" height="792" srcset=" 526w, 199w" sizes="(max-width: 526px) 100vw, 526px" /></p>
  722. <p>So you can see it is like a big long text file, but hidden in all that is my image, my attachment, my Html message (and also a plain text version so that anyone on an old school messaging client can also read it).</p>
  723. <p>Let&#8217;s break down the Message:</p>
  724. <p><strong><span style="text-decoration: underline;">SMTP headers</span></strong></p>
  725. <p>The Message Starts with some SMTP headers which help understand how the message came to be in our inbox! If you are sending receiving MIME in domino, this header information will not be stored in the Body &#8216;RichTextItem&#8217; but is usually stored as separate items on the Document, e.g. the &#8216;Subject&#8217; item, SendTo etc.</p>
  726. <p><img class="aligncenter size-full wp-image-304" src="" alt="SMTPHeaders" width="534" height="166" srcset=" 534w, 300w" sizes="(max-width: 534px) 100vw, 534px" /></p>
  727. <p>Then we get to the actual message body, which is made up of a bunch of Mime Parts. Each mime Part has some headers of it&#8217;s own which describe some things about that mime part, it then has a blank line, and then it has the Content of the Mime Part. Let&#8217;s look at an easy to understand MIME part, the Html message:</p>
  728. <p><img class="aligncenter size-full wp-image-305" src="" alt="MimeTextHtml" width="529" height="74" srcset=" 529w, 300w" sizes="(max-width: 529px) 100vw, 529px" /></p>
  729. <p>So the Mime Part starts with a &#8216;Content-Type&#8217; Header which describes the purpose of this mime part, in our case it is saying &#8216;Please treat this as Html&#8217;, it also says that our charset is UTF-8. Then it has the blank line which indicates we are finished with headers, and now comes the content. The content then begins and you can see the Html content of our email message.</p>
  730. <p>Note in our html content the &lt;img&gt; tag does not use a http URL, but instead uses &#8216;cid&#8217;. This is a mechanism that is used for inline images. The cid refers to a Content-ID of another Mime Part, let&#8217;s have a look at that part:<br />
  731. <img class="aligncenter size-full wp-image-307" src="" alt="MimeImagePng" width="640" height="267" srcset=" 640w, 300w" sizes="(max-width: 640px) 100vw, 640px" /></p>
  732. <p>This mime part represents the little image of the G, it begins with the headers that say &#8220;Treat me like a PNG image, I like to be called &#8216;image.png&#8217;. I am meant to be shown &#8216;inline&#8217; (e.g. embedded, not as a separate attachment), my content is encoded using base64. Some other mime parts refer to me as &#8216;ii_154338a9bb6d446c&#8217;.&#8221;<br />
  733. It then has the blank line, and then the content, which is just the image data in a base64 encoded string.</p>
  734. <p><strong>Multipart Content Types</strong></p>
  735. <p>So we have seen 2 simple MIME Parts, but for a complicated message it also needs to be specified how all our Mime parts fit together, and this is usually done by using the &#8216;multipart&#8217; mime types. Multipart content types are special parts which contain child mime parts.<br />
  736. Multipart types must specify a &#8216;Boundary&#8217;. The boundary is just a text string that is used as a marker to signify when each Child MIME Part begins and ends. This boundary could be anything you like, however if you were to choose a boundary that might also appear in the content, there might be a clash, so the boundary is usually some type of garbled text that is unlikely to appear in a content block.</p>
  737. <p>In our example message we have:</p>
  738. <ul>
  739. <li>multipart/mixed &#8211; usually used to put an attachment, alongside a message body</li>
  740. <li>multipart/related &#8211; usually used to put an inline image alongside a message body</li>
  741. <li>multipart/alternative &#8211; usually used to say &#8216;hey you can used this or that&#8217; e.g. html or plaintext</li>
  742. </ul>
  743. <p>Let&#8217;s look at our multipart/alternative mime part</p>
  744. <p><img class="aligncenter size-full wp-image-308" src="" alt="MimeAlternative" width="526" height="267" srcset=" 526w, 300w" sizes="(max-width: 526px) 100vw, 526px" /></p>
  745. <p>So we can see at the start, the multipart/alternative says &#8220;I will use <em>047d7b414d2a112e990530e95d88</em> as the boundary, and we can see it has used this boundary to define the start and end of the 2 child mime parts (text/plain and text/html) which are to be treated as &#8216;alternatives&#8217; i.e. you could read one or the other. Note also, the very last boundary must have 2 hypens at the end.</p>
  746. <p><strong>Put it all together</strong></p>
  747. <p>So if we were to think of our entire message in terms of a tree like child/sibling structure, we could represent it as such:</p>
  748. <ul>
  749. <li>multipart/mixed
  750. <ul>
  751. <li>multipart/related
  752. <ul>
  753. <li>multipart/alternative
  754. <ul>
  755. <li>text/html</li>
  756. <li>text/plain</li>
  757. </ul>
  758. </li>
  759. <li>image/png  (encoding in base64 display inline)</li>
  760. </ul>
  761. </li>
  762. <li>text/plain (encoded in base64, treat as an attachment)</li>
  763. </ul>
  764. </li>
  765. </ul>
  766. </div>
  767. <div></div>
  768. <div><strong>Learning how to Inspect/Manipulate MIME in Domino</strong></div>
  769. <div>
  770. <div>In Domino, mime parts are  referred to as MIMEEntities, and the MIMEEntity is the class used to represent one of these parts. If this message (above) was stored in a Domino document as a RichTextItem using MIME format, you could access it using:</div>
  771. <div>
  772. <pre class="crayon-plain-tag">MIMEEntity me = document.getMIMEEntity();</pre>
  773. </div>
  774. <div>But, what you would actually get is the <span style="text-decoration: underline;">just the first MimeEntity</span> (in our case the multipart/mixed part). You would then have to traverse through child/sibling to do what you want to do. This topic warrants another post so I won&#8217;t go into depth right now.</div>
  775. <div></div>
  776. <div>IMPORTANT NOTE: If you wish to do some MIME processing on a document you must tell your NotesSession NOT to convert MIME to RichText when it opens a document. This must be done before you retrieve your document. e.g.</div>
  777. <div>
  778. <pre class="crayon-plain-tag">session.setConvertMIME(false);
  780. Document doc = db.getDocumentByUNID(someunid);
  783. MIMEEntity me = doc.getMIMEEntity();</pre><br />
  784. If you don&#8217;t do this, it will look to you as if the MIME was actually stored as RichText (the format) but what is really happened is that Notes is doing a sneaky conversion when you open the document.</p>
  785. <p>This is a NotesSession-wide setting, so it is good practice to check what this setting was before you modify it. That way you can set it back to what it was before you changed it. e.g.</p><pre class="crayon-plain-tag">boolean wasConvertMime = session.isConvertMIME();
  787. // Make sure it won't convert for us
  788. session.setConvertMIME(false);
  792. // Set it back to whatever it was
  793. session.setConvertMIME(wasConvertMime);</pre><p>One last point, is that if you do any manipulation of MIME Entities (we will cover in another post), when you are finished you should use the closeMIMEEntities method to tell the document you are finished mucking around. This method has a &#8216;saveChanges&#8217; parameter which indicates whether you want to keep what you have done, or discard whatever you were doing with MIMEEntities. By default it is &#8216;false&#8217; or discard. This &#8216;savechanges&#8217; is an in-memory process, e.g. it just means apply the changes to the document you have open. You <span style="text-decoration: underline;">still need to save </span>the document to make the changes permanent.</p><pre class="crayon-plain-tag">MIMEEntity me = document.getMIMEEntity();
  797. //Apply the changes to the document
  798. document.closeMIMEEntities(true);
  800. // Save changed document for good
  802. <p>I feel MIME is one of those things that is complicated and simple at the same time. Whenever I have to solve some MIME related problem, I know the answer will be simple, it is usually one line of code in the wrong place, or one bad MIME part header. But, the steps involved to debug that ONE change, are often complicated, and require a good understanding of what is actually going on both in MIME-land and in Domino-land. I hope this post has helped clarify at least some of it for you, please let me know if you have any questions (or if I have made a mistake somewhere)!</p>
  803. </div>
  804. </div>
  805. <div></div>
  806. ]]></content:encoded>
  807. <wfw:commentRss></wfw:commentRss>
  808. <slash:comments>5</slash:comments>
  809. </item>
  810. <item>
  811. <title>Tips for Creating a Webmail UI with XPages</title>
  812. <link></link>
  813. <comments></comments>
  814. <pubDate>Tue, 19 Apr 2016 12:25:50 +0000</pubDate>
  815. <dc:creator><![CDATA[camerongregor]]></dc:creator>
  816. <category><![CDATA[Software Development]]></category>
  817. <category><![CDATA[webmail]]></category>
  818. <category><![CDATA[xpages]]></category>
  820. <guid isPermaLink="false"></guid>
  821. <description><![CDATA[Over the past year my main project has been an XPages application for project-related Email correspondence, with formal document management thrown it as well and a bunch of Action Item / comment functionality surrounding it&#46;&#46;&#46;]]></description>
  822. <content:encoded><![CDATA[<p>Over the past year my main project has been an XPages application for project-related Email correspondence, with formal document management thrown it as well and a bunch of Action Item / comment functionality surrounding it all.</p>
  823. <p>Developing the application as it&#8217;s own email client presented a few different challenges that may not be encountered in normal xpages development. Along the way I have come across a few different gotchas that I thought I better make record of, both the the benefit of others and also for the benefit of the &#8216;Future Cam&#8217; when I forget what I did.</p>
  824. <div>The previous incarnation of this system was a classic notes application. It was purely a repository (or dumping ground) for emails and attachments. Users mail-file templates were modified so that they could &#8216;<em>send and file to repository&#8217;</em>. When external project related emails arrived in their personal inbox, they could &#8216;copy to the repository&#8217;. So all the email composing and viewing was done in the notes client, it was purely a copy-and-file style of application.</div>
  825. <p>In the new XPages system, each project has it&#8217;s own NSF, with a mail-in address. For incoming emails, all project-related correspondence must be sent to this address. For outgoing emails, all emails must be sent from the XPages interface instead of from the user&#8217;s own mail database.</p>
  826. <p>Each topic will be it&#8217;s own separate blog post, I will list the topics below and when the blog posts come out I will link them here.<br />
  827. I have most of these posts 50% done, so if you want to know more about any please leave a comment or message me on twitter and you might just give me the motivation to finish that topic next. Most of this info was hard won, with lots of trial and error, lots of researching, lots of pain, lots of late nights. So I hope you get some benefit from it!</p>
  828. <p>As each topic is done, I will update this list with the link.</p>
  829. <ul>
  830. <li>MIME
  831. <ul>
  832. <li><a href="">You must Learn about Mime!</a></li>
  833. <li>Debugging mime using the MimeInspector (patent pending)</li>
  834. </ul>
  835. </li>
  836. <li>Drafting an Sending Email
  837. <ul>
  838. <li>The many ways to Send an email</li>
  839. <li>Binding to a Java Bean</li>
  840. <li>Autosaving Message drafts</li>
  841. <li>Adding and updating mail signatures</li>
  842. <li><a href="">Preventing pasting of remotely hosted images</a></li>
  843. <li><a href="">Preventing pasting of Data URI images</a></li>
  844. <li>Uploading images as inline attachments when Pasted</li>
  845. <li>Formatting lost when saving a draft message</li>
  846. <li>Using javax.internet.InternetAddress</li>
  847. <li>InputRichText can&#8217;t be used in Embedded Experiences, unless&#8230;but still</li>
  848. </ul>
  849. </li>
  850. <li>Don&#8217;t run ToolsRefreshAllDocs on any of your emails!</li>
  851. <li>Problems with Incoming email messages
  852. <ul>
  853. <li>That Contain a message/rfc822 mime part</li>
  854. <li>That Contain embedded images that won&#8217;t display in XPages</li>
  855. <li>That Contain Attachments you can&#8217;t download with XPages Download</li>
  856. <li>That Contain a &lt;form&gt; element or DOCTYPE</li>
  857. </ul>
  858. </li>
  859. </ul>
  860. ]]></content:encoded>
  861. <wfw:commentRss></wfw:commentRss>
  862. <slash:comments>4</slash:comments>
  863. </item>
  864. <item>
  865. <title>My Slides from AUSLUG 2016 Presentations</title>
  866. <link></link>
  867. <comments></comments>
  868. <pubDate>Fri, 15 Apr 2016 06:29:58 +0000</pubDate>
  869. <dc:creator><![CDATA[camerongregor]]></dc:creator>
  870. <category><![CDATA[Software Development]]></category>
  871. <category><![CDATA[git sourcecontrol xpages control extlib auslug]]></category>
  873. <guid isPermaLink="false"></guid>
  874. <description><![CDATA[This year at AUSLUG I presented 2 sessions. &#8216;Anatomy of a UI Control&#8217; and &#8216;Using Source Control for Domino Development&#8217;. I have just uploaded the slides to the AUSLUG community and thought I would&#46;&#46;&#46;]]></description>
  875. <content:encoded><![CDATA[<p>This year at AUSLUG I presented 2 sessions. &#8216;Anatomy of a UI Control&#8217; and &#8216;Using Source Control for Domino Development&#8217;. I have just uploaded the slides to the AUSLUG community and thought I would also share to the wider world! Slides and description of sessions are below. If you have any questions please let me know! I hope to share more about extension library / control development soon.</p>
  876. <p><strong>Anatomy of a UI Control</strong></p>
  877. <p>This session was designed to just spark a bit of curiosity at how the Extension Library source code is laid out. I suspect that many developers wouldn&#8217;t know where to start when trying to debug a UI Control. The extension library serves as a good example of the various ways you can develop UI Controls. Also with the Extension Library IBM book now several years old, you can no longer rely on it as a complete reference to how to use controls. If you can learn to browse the source code then you have access to understand how the controls work with certainty.<br />
  878. This session dissects the &#8216;Widget Container&#8217; control into is various working parts, and shows the whereabouts within the extension library. The appendix also includes a rough guide to getting set up to browse the extension library source code in eclipse.</p>
  879. <iframe src="" width="400" height="337" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe><br/>
  880. <p><strong>Using Source Control for Domino Development</strong></p>
  881. <p>At AUSLUG 2015 I learned that many Aussie XPages developers were not using source control, so I thought I better give a presentation in 2016. I didn&#8217;t know about it until I saw Declan Sciolla-Lynch presented the topic at AUSLUG 2012, so I thought I better do the same.<br />
  882. Much of the session was a live demonstration (which somehow went smoothly!) so the slides are only really from the first half of the session.</p>
  883. <iframe src="" width="400" height="337" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe><br/>
  884. ]]></content:encoded>
  885. <wfw:commentRss></wfw:commentRss>
  886. <slash:comments>0</slash:comments>
  887. </item>
  888. <item>
  889. <title>Associating *.theme and *.xsp-config with Eclipse XML Editor</title>
  890. <link></link>
  891. <comments></comments>
  892. <pubDate>Wed, 09 Mar 2016 07:47:22 +0000</pubDate>
  893. <dc:creator><![CDATA[camerongregor]]></dc:creator>
  894. <category><![CDATA[Software Development]]></category>
  895. <category><![CDATA[eclipse]]></category>
  896. <category><![CDATA[plugin]]></category>
  897. <category><![CDATA[xml]]></category>
  898. <category><![CDATA[xpages]]></category>
  900. <guid isPermaLink="false"></guid>
  901. <description><![CDATA[When you do XPages OSGi Library development in eclipse, you will often have to edit xml files (or xhtml) that have a different file extension than .xml These are files such as XPages theme&#46;&#46;&#46;]]></description>
  902. <content:encoded><![CDATA[<p>When you do XPages OSGi Library development in eclipse, you will often have to edit xml files (or xhtml) that have a different file extension than <strong>.xml</strong></p>
  903. <p>These are files such as XPages theme files with the <strong>.theme</strong> extension, and Xsp control configuration files with the <strong>.xsp-config</strong> extension.</p>
  904. <p>Unfortunately these file extensions are not associated with the XML Editor by default and this causes undesirable behaviour when you double-click them to open them for editing.</p>
  905. <p>When you attempt to open a <strong>.theme</strong> file, Eclipse thinks you want to <em>apply</em> an eclipse theme and you end up with this lovely error.</p>
  906. <p><img class="alignnone size-full wp-image-271" src="" alt="cantapply" width="655" height="195" srcset=" 655w, 300w" sizes="(max-width: 655px) 100vw, 655px" /></p>
  907. <p>Opening the xsp-config file is just as fun. First if you double click you get notepad (on windows)</p>
  908. <p>&nbsp;</p>
  909. <p><img class="alignnone size-full wp-image-272" src="" alt="opennotepad" width="789" height="334" srcset=" 789w, 300w, 768w" sizes="(max-width: 789px) 100vw, 789px" /></p>
  910. <p>So you might instead try to Open With -&gt; Xml Editor</p>
  911. <p><img class="alignnone size-full wp-image-273" src="" alt="openwithxml" width="1020" height="467" srcset=" 1020w, 300w, 768w" sizes="(max-width: 1020px) 100vw, 1020px" /></p>
  912. <p>&nbsp;</p>
  913. <p>but then you get this:</p>
  914. <p><img class="alignnone size-full wp-image-274" src="" alt="unsupported" width="374" height="177" srcset=" 374w, 300w, 520w, 720w" sizes="(max-width: 374px) 100vw, 374px" /></p>
  915. <p>and end up with the normal text editor (with no pretty colours!)</p>
  916. <p><img class="alignnone size-full wp-image-275" src="" alt="xspconftext" width="558" height="539" srcset=" 558w, 300w" sizes="(max-width: 558px) 100vw, 558px" /></p>
  917. <p>So how do we fix this? We have to add these as content types in the preferences section as below.</p>
  918. <p>Find the &#8216;XML&#8217; Content Type and select it, click &#8216;Add..&#8217; in the File associations section, type in the new extension to associate and click ok</p>
  919. <p>In the picture below I am doing it for *.theme files and you can also see I have already done it for *.xsp-config</p>
  920. <p><img class="alignnone size-full wp-image-276" src="" alt="addcontenttype" width="872" height="591" srcset=" 872w, 300w, 768w" sizes="(max-width: 872px) 100vw, 872px" /></p>
  921. <p>&nbsp;</p>
  922. <p>Now let&#8217;s see what happens when we double click our theme file:</p>
  923. <p><img class="alignnone size-full wp-image-277" src="" alt="theme working" width="728" height="308" srcset=" 728w, 300w" sizes="(max-width: 728px) 100vw, 728px" /></p>
  924. <p>We have our pretty colours and XML editor!</p>
  925. <p>and xsp-config?</p>
  926. <p><img class="alignnone size-full wp-image-278" src="" alt="xsp-config working" width="675" height="194" srcset=" 675w, 300w" sizes="(max-width: 675px) 100vw, 675px" /></p>
  927. <p>&nbsp;</p>
  928. <p>And if you are crazy like me and are playing around with loading xpages from within a plugin using the <a href="" target="_blank">XPages Bazaar</a> then maybe you will do it for <strong>.xsp</strong> too <img src="" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
  929. <p><img class="alignnone size-full wp-image-279" src="" alt="xsp view working" width="593" height="299" srcset=" 593w, 300w" sizes="(max-width: 593px) 100vw, 593px" /></p>
  930. <p>&nbsp;</p>
  931. ]]></content:encoded>
  932. <wfw:commentRss></wfw:commentRss>
  933. <slash:comments>0</slash:comments>
  934. </item>
  935. <item>
  936. <title>Inspect Component Properties using Component Binding</title>
  937. <link></link>
  938. <comments></comments>
  939. <pubDate>Tue, 08 Mar 2016 08:14:23 +0000</pubDate>
  940. <dc:creator><![CDATA[camerongregor]]></dc:creator>
  941. <category><![CDATA[Software Development]]></category>
  942. <category><![CDATA[control]]></category>
  943. <category><![CDATA[renderer]]></category>
  944. <category><![CDATA[xpages]]></category>
  946. <guid isPermaLink="false"></guid>
  947. <description><![CDATA[Inspired by Paul Withers recent component binding post, I discovered another use for component binding whilst I was preparing for my upcoming presentation &#8216;Anatomy of a UI Control&#8217; this Thursday at AUSLUG. In my presentation&#46;&#46;&#46;]]></description>
  948. <content:encoded><![CDATA[<p>Inspired by Paul Withers <a href="" target="_blank">recent component binding post</a>, I discovered another use for component binding whilst I was preparing for my upcoming presentation &#8216;Anatomy of a UI Control&#8217; this Thursday at AUSLUG.</p>
  949. <p>In my presentation I demonstrate how to determine a component&#8217;s <strong>Component Family</strong> and it&#8217;s <strong>Renderer Type </strong>which are both essential to determine which renderer is selected for the component by XPages.</p>
  950. <p>It is extremely similar purpose to a technique that was previously demonstrated by the late Tim Tripcony on his blog. Tim demonstrated how to <a href="" target="_blank">determine a component&#8217;s StyleKitFamily</a>, which is an important setting when applying theme properties.</p>
  951. <p>All I have done is rejig the concept to use component binding to link the component to the computed values which output the properties.</p>
  952. <p>In this example I am inspecting the settings of the widget Container, however if you want to inspect a different component, then remove the widgetcontainer, replace it with whatever component you want to inspect, and make sure the &#8216;binding&#8217; property is set the same as it was with the widgetcontainer.</p><pre class="crayon-plain-tag">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
  953. &lt;xp:view xmlns:xp="" xmlns:xe=""&gt;
  955. &lt;!--
  956. Bind the Component that you want to inspect to a request scope
  957. variable by using the binding property We Will use myComponent as
  958. scope variable name
  959. --&gt;
  960. &lt;xe:widgetContainer id="widgetContainer1"
  961. titleBarText="Hi there" binding="#{requestScope.myComponent}"&gt;
  962. &lt;/xe:widgetContainer&gt;
  964. &lt;!-- Output the properties of that Component --&gt;
  965. &lt;xp:table&gt;
  966. &lt;xp:tr&gt;
  967. &lt;xp:td&gt;
  968. &lt;xp:label value="Component Family" id="label1"&gt;&lt;/xp:label&gt;
  969. &lt;/xp:td&gt;
  970. &lt;xp:td&gt;
  971. &lt;!-- Output the Components Component Family using getFamily()  --&gt;
  972. &lt;xp:text escape="true" id="computedField1"
  973. value="#{}"&gt;&lt;/xp:text&gt;
  974. &lt;/xp:td&gt;
  975. &lt;/xp:tr&gt;
  976. &lt;xp:tr&gt;
  977. &lt;xp:td&gt;
  978. &lt;xp:label value="Renderer Type" id="label2"&gt;&lt;/xp:label&gt;
  979. &lt;/xp:td&gt;
  980. &lt;xp:td&gt;
  981. &lt;!--
  982. Output the Components Renderer Type using getRendererType()
  983. rendererType in EL Notation
  984. --&gt;
  985. &lt;xp:text escape="true" id="computedField2"
  986. value="#{requestScope.myComponent.rendererType}"&gt;&lt;/xp:text&gt;
  988. &lt;/xp:td&gt;
  989. &lt;/xp:tr&gt;
  990. &lt;xp:tr&gt;
  991. &lt;xp:td&gt;
  992. &lt;xp:label value="StyleKit Family" id="label3"&gt;&lt;/xp:label&gt;
  993. &lt;/xp:td&gt;
  994. &lt;xp:td&gt;
  995. &lt;!--
  996. Output the Components Style Kit Family using getStyleKitFamily()
  997. styleKitFamily in EL Notation
  998. --&gt;
  999. &lt;xp:text escape="true" id="computedField3"
  1000. value="#{requestScope.myComponent.styleKitFamily}"&gt;&lt;/xp:text&gt;
  1001. &lt;/xp:td&gt;
  1002. &lt;/xp:tr&gt;
  1004. &lt;/xp:table&gt;
  1006. &lt;/xp:view&gt;</pre><p>When the Bootstrap theme is applied to the application, the properties are as follows:</p>
  1007. <p><img class="alignnone size-full wp-image-268" src="" alt="widgetContainer" width="566" height="232" srcset=" 566w, 300w" sizes="(max-width: 566px) 100vw, 566px" /></p>
  1008. <p>Obviously with different themes applied you might get different renderer types for the same component.</p>
  1009. <p>I have added it as an <a href="" target="_blank">XSnippet</a></p>
  1010. <p>At the end of the day, this is not really groundbreaking stuff! You could achieve the same result beforehand with a few SSJS computed values as follows:</p><pre class="crayon-plain-tag">var c = getComponent("widgetContainer1");
  1011. return c.getComponentFamily();</pre><p>However if you are weird like me and prefer to use EL because it looks nicer in the markup, then using the Component Binding might be an option for you!</p>
  1012. <p>&nbsp;</p>
  1013. ]]></content:encoded>
  1014. <wfw:commentRss></wfw:commentRss>
  1015. <slash:comments>8</slash:comments>
  1016. </item>
  1017. </channel>
  1018. </rss>

If you would like to create a banner that links to this page (i.e. this validation result), do the following:

  1. Download the "valid RSS" banner.

  2. Upload the image to your own server. (This step is important. Please do not link directly to the image on this server.)

  3. Add this HTML to your page (change the image src attribute if necessary):

If you would like to create a text link instead, here is the URL you can use:

Copyright © 2002-9 Sam Ruby, Mark Pilgrim, Joseph Walton, and Phil Ringnalda