Congratulations!

[Valid RSS] This is a valid RSS feed.

Recommendations

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

Source: http://camerongregor.com/category/software-development/feed/

  1. <?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
  2. xmlns:content="http://purl.org/rss/1.0/modules/content/"
  3. xmlns:wfw="http://wellformedweb.org/CommentAPI/"
  4. xmlns:dc="http://purl.org/dc/elements/1.1/"
  5. xmlns:atom="http://www.w3.org/2005/Atom"
  6. xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
  7. xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
  8. >
  9.  
  10. <channel>
  11. <title>Software Development &#8211; Cameron Gregor</title>
  12. <atom:link href="http://camerongregor.com/category/software-development/feed/" rel="self" type="application/rss+xml" />
  13. <link>http://camerongregor.com</link>
  14. <description>Sharing Tips, Tools and Techniques for XPages Developers</description>
  15. <lastBuildDate>Tue, 21 Mar 2017 13:15:32 +0000</lastBuildDate>
  16. <language>en-AU</language>
  17. <sy:updatePeriod>hourly</sy:updatePeriod>
  18. <sy:updateFrequency>1</sy:updateFrequency>
  19. <generator>https://wordpress.org/?v=4.7.4</generator>
  20. <item>
  21. <title>Auto-width Bootstrap Column XPages Controls</title>
  22. <link>http://camerongregor.com/2017/03/22/auto-width-bootstrap-column-xpages-controls/</link>
  23. <comments>http://camerongregor.com/2017/03/22/auto-width-bootstrap-column-xpages-controls/#respond</comments>
  24. <pubDate>Tue, 21 Mar 2017 13:13:18 +0000</pubDate>
  25. <dc:creator><![CDATA[camerongregor]]></dc:creator>
  26. <category><![CDATA[Software Development]]></category>
  27. <category><![CDATA[bootstrap]]></category>
  28. <category><![CDATA[control]]></category>
  29. <category><![CDATA[xpages]]></category>
  30.  
  31. <guid isPermaLink="false">http://camerongregor.com/?p=479</guid>
  32. <description><![CDATA[I&#8217;ve been stuck working with OneUI Version 3 for the past couple of years, due to a regretful decision made at the beginning of my major project. OneUI was better than nothing but very frustrating&#46;&#46;&#46;]]></description>
  33. <content:encoded><![CDATA[<p>I&#8217;ve been stuck working with <a href="http://infolib.lotus.com/resources/oneui/3.0/docPublic/examples.htm?content=2colGrid.htm" target="_blank">OneUI Version 3</a> for the past couple of years, due to a regretful decision made at the beginning of my major project. OneUI was better than nothing but very frustrating at times.<br />
  34. Finally, I have moved on to my next project and I am now using bootstrap (version 3)</p>
  35. <p>A common task when laying out a page using bootstrap is to divide sections up into rows and columns, and use the appropriate css styles to do so. I&#8217;m going to assume you are familiar with bootstrap&#8217;s grid system, and if not I recommend you <a href="http://getbootstrap.com/css/#grid" target="_blank">hearing it from the horse&#8217;s mouth</a> instead of me trying to explain it again.</p>
  36. <p>I like creating XspLibrary controls, so I decided I would go ahead and create row and column controls. It could be seen as a little overkill and you can easily live without it, but I see the <a href="http://showcase.bootsfaces.net/layout/basic.jsf" target="_blank">Bootfaces project</a> did it, so I took inspiration from their container, row and column controls.</p>
  37. <p>I then also added the ability for the columns to have their widths automatically distributed. Auto-widths is a feature that is <a href="https://v4-alpha.getbootstrap.com/layout/grid/#auto-layout-columns" target="_blank">coming with bootstrap 4</a>,  but for now we need to do it ourselves!</p>
  38. <h2>Example XPages Markup</h2>
  39. <p>The following examples end up looking like this: (<strong>Note:</strong> the blue border is just added for demo purposes to show the outline of the columns)</p>
  40. <p><img class="aligncenter size-full wp-image-481" src="http://camerongregor.com/wp-content/uploads/2017/03/BootstrapDemoPage.jpg" alt="" width="981" height="140" srcset="http://camerongregor.com/wp-content/uploads/2017/03/BootstrapDemoPage.jpg 981w, http://camerongregor.com/wp-content/uploads/2017/03/BootstrapDemoPage-300x43.jpg 300w, http://camerongregor.com/wp-content/uploads/2017/03/BootstrapDemoPage-768x110.jpg 768w" sizes="(max-width: 981px) 100vw, 981px" /></p>
  41. <p>Here is a simple example of 2-column layout with equal width columns</p><pre class="crayon-plain-tag">&lt;gb:row id="row1"&gt;
  42.    &lt;gb:column id="column1"&gt;These two columns&lt;/gb:column&gt;
  43.    &lt;gb:column id="column2"&gt;are Equal Width&lt;/gb:column&gt;
  44. &lt;/gb:row&gt;</pre><p>Notice above how I haven&#8217;t specified any column widths? The columns will all become equal width, so in the example above, they will both be 6 columns wide (12 divided by 2 columns).</p>
  45. <p>If there have 3 columns, then they would each automatically be 4 columns wide (12 divided by 3 columns).</p><pre class="crayon-plain-tag">&lt;gb:row id="row5"&gt;
  46.    &lt;gb:column id="column3"&gt;These three &lt;/gb:column&gt;
  47.    &lt;gb:column id="column10"&gt;columns are also&lt;/gb:column&gt;
  48.    &lt;gb:column id="column11"&gt;Equal Width&lt;/gb:column&gt;
  49. &lt;/gb:row&gt;</pre><p></p>
  50. <h3>Specifying a column width</h3>
  51. <p>If you want a column to be a specifc width, you use the span property to specify the width for that column, the remaining columns will receive their fair share of whatever is left over.</p><pre class="crayon-plain-tag">&lt;gb:row id="row2"&gt;
  52.    &lt;gb:column id="column4"&gt;These columns have&lt;/gb:column&gt;
  53.    &lt;gb:column id="column5" span="6"&gt;the middle with span="6"&lt;/gb:column&gt;
  54.    &lt;gb:column id="column6"&gt;and the outer columns are auto width&lt;/gb:column&gt;
  55. &lt;/gb:row&gt;</pre><p></p>
  56. <h3>Specifying alignment</h3>
  57. <p>I&#8217;ve also added an align property so I can quickly align the contents.</p><pre class="crayon-plain-tag">&lt;gb:row id="row4"&gt;
  58.    &lt;gb:column id="column7" align="left"&gt;This is align left (default)&lt;/gb:column&gt;
  59.    &lt;gb:column id="column8" align="center"&gt;This is align middle&lt;/gb:column&gt;
  60.    &lt;gb:column id="column9" align="right"&gt;This is align left&lt;/gb:column&gt;
  61. &lt;/gb:row&gt;</pre><p></p>
  62. <h3>Full Row column</h3>
  63. <p>If you want to use the entire row, then you can just leave the column definition out entirely and the contents will automatically be wrapped in a col-md-12 column</p><pre class="crayon-plain-tag">&lt;gb:row id="row3"&gt;
  64. If you want a Full Row, then just don't include columns
  65. &lt;/gb:row&gt;</pre><p></p>
  66. <h3>Using style and styleClass</h3>
  67. <p>You can still use style and styleClass properties as you normally would.</p>
  68. <h3>Container Control</h3>
  69. <p>If you are using one of the extension library bootstrap applicationLayouts, then you won&#8217;t need to use the container control because it is already covered by that. But, If you are rolling your own you can use the container layout which has a fluid property to control whether is is a bootstrap fluid layout or not.</p><pre class="crayon-plain-tag">&lt;gb:container id="container1" fluid="true"&gt;
  70.  
  71.    &lt;!-- rows, columns, your content --&gt;
  72.  
  73. &lt;/gb:container&gt;</pre><p></p>
  74. <h2>Overview of creating the controls</h2>
  75. <p>Here is what we are going to do:</p>
  76. <ol>
  77. <li>Create the Container UIComponent</li>
  78. <li>Create the Column UIComponent</li>
  79. <li>Create the Row UIComponent</li>
  80. <li>Provide Control Definitions via an xsp-config file so that designer knows how to use them</li>
  81. <li>Program in the auto-width algorithm</li>
  82. </ol>
  83. <p>Notice we are not going to create a renderer. We will be extending the panel control, and the existing panel renderer will be used automatically.</p>
  84. <p>For each component we are just going to <em>extend </em>the existing <strong>&lt;xp:panel&gt;</strong> control to re-use all the existing functionality, and just add a few extra properties.</p>
  85. <h3>Creating the Container Component</h3>
  86. <p>The container component is simply an extension of the panel control (UIPanelEx) with an additional property &#8216;fluid&#8217;. We add the getters and setters for the fluid property, and make sure it is included in the restore and save state operations.</p>
  87. <p>We also override the getStyleClass method so that it will always concatenate &#8216;container&#8217; or &#8216;container-fluid&#8217; depending on the fluid property.</p><pre class="crayon-plain-tag">package com.gregorbyte.xsp.component.bootstrap;
  88.  
  89. import javax.faces.context.FacesContext;
  90. import javax.faces.el.ValueBinding;
  91.  
  92. import com.gregorbyte.xsp.util.GregorbyteUtil;
  93. import com.ibm.xsp.component.UIPanelEx;
  94.  
  95. public class UIContainer extends UIPanelEx {
  96.  
  97. public Boolean fluid;
  98.  
  99. public UIContainer() {
  100.  
  101. }
  102.  
  103. @Override
  104. public String getStyleClass() {
  105.  
  106. String parent = super.getStyleClass();
  107.  
  108. if (isFluid()) {
  109. return GregorbyteUtil.concatStyleClasses("container-fluid", parent);
  110. } else {
  111. return GregorbyteUtil.concatStyleClasses("container", parent);
  112. }
  113.  
  114. }
  115.  
  116. public Boolean isFluid() {
  117.  
  118. if (this.fluid != null) {
  119. return this.fluid;
  120. }
  121.  
  122. ValueBinding vb = getValueBinding("fluid");
  123.  
  124. if (vb != null) {
  125. return (Boolean) vb.getValue(getFacesContext());
  126. }
  127.  
  128. return false;
  129.  
  130. }
  131.  
  132. public void setFluid(Boolean fluid) {
  133. this.fluid = fluid;
  134. }
  135.  
  136. @Override
  137. public void restoreState(FacesContext context, Object state) {
  138.  
  139. Object[] values = (Object[]) state;
  140.  
  141. super.restoreState(context, values[0]);
  142. fluid = (Boolean) values[1];
  143.  
  144. }
  145.  
  146. @Override
  147. public Object saveState(FacesContext context) {
  148.  
  149. Object[] values = new Object[2];
  150.  
  151. values[0] = super.saveState(context);
  152. values[1] = fluid;
  153.  
  154. return values;
  155.  
  156. }
  157.  
  158. }</pre><p></p>
  159. <h3>Creating the Column Component</h3>
  160. <p>The column component is a little more complex. We are also going to extend the UIPanelEx control but we will also add some control properties</p>
  161. <ul>
  162. <li><strong>span</strong> &#8211; This will be an integer that can be set to specify the column span of the bootstrap grid</li>
  163. <li><strong>align</strong> &#8211; This will be a String which can be &#8216;left&#8217; (Default), &#8216;center&#8217; or &#8216;right&#8217;. This will then add the necessary bootstrap css class to perform aligning of the contents</li>
  164. </ul>
  165. <p>I am also going to add a property <strong>guessSpan </strong>which is not exposed to domino designer and will only be used to store our &#8216;guesstimate&#8217; column span when calculating the auto widths.</p>
  166. <p>Then, I create a private method &#8216;getEffectiveSpan&#8217; which will check if a column span has been explicitly set with the <strong>span</strong> property and if so it will use that. If a span was not set, it will use the &#8216;guessSpan&#8217; that was guessed via the algorithm</p>
  167. <p>Then we override the &#8216;getStyleClass&#8217; method, so that when the renderer comes along and asks for the styleClasses, we can inject some styleClasses of our own.</p>
  168. <p>We are using the very handy method from Extension Library&#8217;s ExtLibUtil, which allows us to easily concatenate styles. I have copied the method into my own Util class so that there will be no dependency on the Extension Library.</p>
  169. <p>First we ask for any styles that have been explicitly set. Then we concatenate our column style from the bootstrap grid system e.g. &#8220;col-md-8&#8221;, and then we concatenate an alignment style if one was set on the column.</p><pre class="crayon-plain-tag">package com.gregorbyte.xsp.component.bootstrap;
  170.  
  171. import javax.faces.context.FacesContext;
  172. import javax.faces.el.ValueBinding;
  173.  
  174. import com.gregorbyte.xsp.util.GregorbyteUtil;
  175. import com.ibm.commons.util.StringUtil;
  176. import com.ibm.xsp.component.UIPanelEx;
  177.  
  178. public class UIColumn extends UIPanelEx {
  179.  
  180. private Integer guessSpan = null;
  181.  
  182. // Defaults for Medium Columns, use styleClass for specific layouts
  183. private Integer span = null;
  184.  
  185. private String align = null;
  186.  
  187. public UIColumn() {
  188. super();
  189. }
  190.  
  191. private Integer getEffectiveSpan() {
  192. if (getSpan() != null) {
  193. return getSpan();
  194. } else if (getGuessSpan() != null) {
  195. return getGuessSpan();
  196. }
  197.  
  198. return 12;
  199. }
  200.  
  201. @Override
  202. public String getStyleClass() {
  203.  
  204. String styleClass = super.getStyleClass();
  205.  
  206. styleClass = GregorbyteUtil.concatStyleClasses(styleClass, "col-md-" + getEffectiveSpan());
  207.  
  208. String align = getAlign();
  209.  
  210. if (StringUtil.equals(align, "left")) {
  211. styleClass = GregorbyteUtil.concatStyleClasses(styleClass, "text-left");
  212. } else if (StringUtil.equals(align, "right")) {
  213. styleClass = GregorbyteUtil.concatStyleClasses(styleClass, "text-right");
  214. } else if (StringUtil.equals(align, "center")) {
  215. styleClass = GregorbyteUtil.concatStyleClasses(styleClass, "text-center");
  216. }
  217.  
  218. return styleClass;
  219.  
  220. }
  221.  
  222. public Integer getGuessSpan() {
  223. return guessSpan;
  224. }
  225.  
  226. public void setGuessSpan(Integer guessSpan) {
  227. this.guessSpan = guessSpan;
  228. }
  229.  
  230. public Integer getSpan() {
  231.  
  232. if (this.span != null) {
  233. return this.span;
  234. }
  235.  
  236. ValueBinding vb = getValueBinding("span");
  237.  
  238. if (vb != null) {
  239. return (Integer) vb.getValue(getFacesContext());
  240. }
  241.  
  242. return null;
  243.  
  244. }
  245.  
  246. public void setSpan(Integer span) {
  247. this.span = span;
  248. }
  249.  
  250. public String getAlign() {
  251.  
  252. if (this.align != null) {
  253. return this.align;
  254. }
  255.  
  256. ValueBinding vb = getValueBinding("align");
  257.  
  258. if (vb != null) {
  259. return (String) vb.getValue(getFacesContext());
  260. }
  261.  
  262. return null;
  263.  
  264. }
  265.  
  266. public void setAlign(String align) {
  267. this.align = align;
  268. }
  269.  
  270. @Override
  271. public void restoreState(FacesContext context, Object state) {
  272.  
  273. Object[] values = (Object[]) state;
  274.  
  275. super.restoreState(context, values[0]);
  276.  
  277. guessSpan = (Integer) values[1];
  278. span = (Integer) values[2];
  279. align = (String) values[3];
  280.  
  281. }
  282.  
  283. @Override
  284. public Object saveState(FacesContext context) {
  285.  
  286. Object[] values = new Object[4];
  287.  
  288. values[0] = super.saveState(context);
  289. values[1] = guessSpan;
  290. values[2] = span;
  291. values[3] = align;
  292.  
  293. return values;
  294.  
  295. }
  296.  
  297. }</pre><p></p>
  298. <h3>Creating the Row Component</h3>
  299. <p>The row component is also an extension of the panel control. But we are going to add some extra behaviour to it. Just before the Row&#8217;s children (the columns) are Rendered we are going to calculate the necessary spans for any columns that did not have a span. If we can&#8217;t find any columns as children, then we will assume that we are supposed to wrap all the contents into a single column and therefore we will render a start and end tag for a &lt;div class=&#8221;col-md-12&#8243;&gt;</p>
  300. <h3>Programming the auto-width algorithm</h3>
  301. <p>The auto-width algorithm does the following</p>
  302. <ol>
  303. <li>Finds the total span of columns that have been explicitly allocated using the <strong>span</strong> property</li>
  304. <li>Figures out the remaining span available (e.g. 12 &#8211; allocated)</li>
  305. <li>Figures out the average span to allocate to remaining columns without a span</li>
  306. <li>Allocates the &#8216;guessed&#8217; span to the remaining columns</li>
  307. </ol>
  308. <p></p><pre class="crayon-plain-tag">package com.gregorbyte.xsp.component.bootstrap;
  309.  
  310. import java.io.IOException;
  311. import java.util.ArrayList;
  312. import java.util.List;
  313.  
  314. import javax.faces.FacesException;
  315. import javax.faces.component.UIComponent;
  316. import javax.faces.context.FacesContext;
  317.  
  318. import com.gregorbyte.xsp.util.GregorbyteUtil;
  319. import com.ibm.xsp.component.UIPanelEx;
  320. import com.ibm.xsp.util.TypedUtil;
  321.  
  322. public class UIRow extends UIPanelEx {
  323.  
  324. private Boolean autoColumn = false;
  325.  
  326. public UIRow() {
  327. super();
  328.  
  329. }
  330.  
  331. @Override
  332. public String getStyleClass() {
  333.  
  334. String parent = super.getStyleClass();
  335.  
  336. return GregorbyteUtil.concatStyleClasses("row", parent);
  337.  
  338. }
  339.  
  340. @Override
  341. public void encodeBegin(FacesContext context) throws IOException {
  342.  
  343. super.encodeBegin(context);
  344.  
  345. calculateColumnSpans(context);
  346. if (autoColumn) {
  347. context.getResponseWriter().startElement("div", null);
  348. context.getResponseWriter().writeAttribute("class", "col-md-12", null);
  349. }
  350.  
  351. }
  352.  
  353. @Override
  354. public void encodeEnd(FacesContext context) throws IOException {
  355.  
  356. if (autoColumn) {
  357. context.getResponseWriter().endElement("div");
  358. }
  359.  
  360. super.encodeEnd(context);
  361. }
  362.  
  363. public void calculateColumnSpans(FacesContext context) throws FacesException {
  364.  
  365. List&lt;UIComponent&gt; kids = TypedUtil.getChildren(this);
  366.  
  367. List&lt;UIColumn&gt; cols = new ArrayList&lt;UIColumn&gt;();
  368.  
  369. int totalMediumSpecified = 0;
  370.  
  371. for (UIComponent comp : kids) {
  372. if (comp instanceof UIColumn) {
  373.  
  374. UIColumn col = (UIColumn) comp;
  375.  
  376. if (col.getSpan() != null) {
  377. totalMediumSpecified += col.getSpan();
  378. } else {
  379. cols.add(col);
  380. }
  381.  
  382. }
  383. }
  384.  
  385. // If No Columns we will render
  386. autoColumn = (cols.size() == 0);
  387.  
  388. if (autoColumn)
  389. return;
  390.  
  391. int remaining = 12 - totalMediumSpecified;
  392.  
  393. Integer colGuess = remaining / cols.size();
  394.  
  395. if (colGuess != null) {
  396. for (UIColumn uiColumn : cols) {
  397. uiColumn.setGuessSpan(colGuess);
  398. }
  399. }
  400.  
  401. }
  402.  
  403. @Override
  404. public void restoreState(FacesContext context, Object state) {
  405.  
  406. Object[] values = (Object[]) state;
  407.  
  408. super.restoreState(context, values[0]);
  409. autoColumn = (Boolean) values[1];
  410.  
  411. }
  412.  
  413. @Override
  414. public Object saveState(FacesContext context) {
  415.  
  416. Object[] values = new Object[2];
  417.  
  418. values[0] = super.saveState(context);
  419. values[1] = autoColumn;
  420.  
  421. return values;
  422.  
  423. }
  424.  
  425. }</pre><p></p>
  426. <h2>Provide Component Definition via xsp-config file</h2>
  427. <p>The xsp-config file tells Domino Designer everything it needs to know to include it on an XPage so we need to specify the properties that can be set.</p>
  428. <p>The file starts with namespace definition to say &#8216;all component definitions in this file are under this namespace&#8217; and then it specifies the 2 components.</p>
  429. <p>We provide the description, tagname, display name, corresponding java class (so it knows how to instantiate it) a component type (usually just the same as the class).</p>
  430. <p>Then we specify the properties that can be set. You can see for the row there is no extra properties, but for the column we have added the <strong>span</strong> and <strong>align </strong>properties and have provided combo options Left, Center and Right for the <strong>align</strong> property. For the container component, we have specified the <strong>fluid</strong> property.</p><pre class="crayon-plain-tag">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
  431. &lt;faces-config&gt;
  432.  
  433. &lt;faces-config-extension&gt;
  434. &lt;namespace-uri&gt;http://www.gregorbyte.com/xsp/&lt;/namespace-uri&gt;
  435. &lt;default-prefix&gt;gb&lt;/default-prefix&gt;
  436. &lt;/faces-config-extension&gt;
  437.  
  438. &lt;component&gt;
  439.  
  440. &lt;description&gt;Row Container for Bootstrap Columns
  441. &lt;/description&gt;
  442. &lt;display-name&gt;Bootstrap Row&lt;/display-name&gt;
  443. &lt;component-type&gt;com.gregorbyte.xsp.bootstrap.Row
  444. &lt;/component-type&gt;
  445. &lt;component-class&gt;com.gregorbyte.xsp.component.bootstrap.UIRow
  446. &lt;/component-class&gt;
  447.  
  448. &lt;component-extension&gt;
  449.  
  450. &lt;base-component-type&gt;com.ibm.xsp.Panel
  451. &lt;/base-component-type&gt;
  452.  
  453. &lt;component-family&gt;javax.faces.Panel
  454. &lt;/component-family&gt;
  455.  
  456. &lt;tag-name&gt;row&lt;/tag-name&gt;
  457.  
  458. &lt;designer-extension&gt;
  459. &lt;in-palette&gt;true&lt;/in-palette&gt;
  460. &lt;category&gt;Gregorbyte Library&lt;/category&gt;
  461. &lt;/designer-extension&gt;
  462.  
  463. &lt;/component-extension&gt;
  464.  
  465. &lt;/component&gt;
  466.  
  467. &lt;component&gt;
  468.  
  469. &lt;description&gt;Bootstrap Columns
  470. &lt;/description&gt;
  471. &lt;display-name&gt;Bootstrap Column&lt;/display-name&gt;
  472. &lt;component-type&gt;com.gregorbyte.xsp.bootstrap.Column
  473. &lt;/component-type&gt;
  474. &lt;component-class&gt;com.gregorbyte.xsp.component.bootstrap.UIColumn
  475. &lt;/component-class&gt;
  476.  
  477. &lt;property&gt;
  478. &lt;description&gt;Adds an Alignment class to the column &lt;/description&gt;
  479. &lt;display-name&gt;Alignment&lt;/display-name&gt;
  480. &lt;property-name&gt;align&lt;/property-name&gt;
  481. &lt;property-class&gt;java.lang.String&lt;/property-class&gt;
  482. &lt;property-extension&gt;
  483. &lt;designer-extension&gt;
  484. &lt;editor&gt;com.ibm.workplace.designer.property.editors.comboParameterEditor&lt;/editor&gt;
  485. &lt;editor-parameter&gt;
  486. left
  487. center
  488. right
  489. &lt;/editor-parameter&gt;
  490. &lt;category&gt;basics&lt;/category&gt;
  491. &lt;/designer-extension&gt;
  492. &lt;/property-extension&gt;
  493. &lt;/property&gt;
  494.  
  495. &lt;property&gt;
  496. &lt;description&gt;Number of Columns to Span (Medium Screen size). Leave blank for auto calculation&lt;/description&gt;
  497. &lt;display-name&gt;Columns to Span&lt;/display-name&gt;
  498. &lt;property-name&gt;span&lt;/property-name&gt;
  499. &lt;property-class&gt;java.lang.Integer&lt;/property-class&gt;
  500. &lt;property-extension&gt;
  501. &lt;allow-run-time-binding&gt;true&lt;/allow-run-time-binding&gt;
  502. &lt;designer-extension&gt;
  503. &lt;category&gt;basics&lt;/category&gt;
  504. &lt;/designer-extension&gt;
  505. &lt;/property-extension&gt;
  506. &lt;/property&gt;
  507.  
  508. &lt;component-extension&gt;
  509.  
  510. &lt;base-component-type&gt;com.ibm.xsp.Panel
  511. &lt;/base-component-type&gt;
  512.  
  513. &lt;component-family&gt;javax.faces.Panel
  514. &lt;/component-family&gt;
  515.  
  516. &lt;tag-name&gt;column&lt;/tag-name&gt;
  517.  
  518. &lt;designer-extension&gt;
  519. &lt;in-palette&gt;true&lt;/in-palette&gt;
  520. &lt;category&gt;Gregorbyte Library&lt;/category&gt;
  521. &lt;/designer-extension&gt;
  522.  
  523. &lt;/component-extension&gt;
  524.  
  525. &lt;/component&gt;
  526.  
  527. &lt;component&gt;
  528.  
  529. &lt;description&gt;Container for Bootstrap Layout
  530. &lt;/description&gt;
  531. &lt;display-name&gt;Bootstrap Container&lt;/display-name&gt;
  532. &lt;component-type&gt;com.gregorbyte.xsp.bootstrap.Container
  533. &lt;/component-type&gt;
  534. &lt;component-class&gt;com.gregorbyte.xsp.component.bootstrap.UIContainer
  535. &lt;/component-class&gt;
  536.  
  537. &lt;property&gt;
  538. &lt;description&gt;Use Fluid Layout
  539. &lt;/description&gt;
  540. &lt;display-name&gt;Use Fluid Layout&lt;/display-name&gt;
  541. &lt;property-name&gt;fluid&lt;/property-name&gt;
  542. &lt;property-class&gt;java.lang.Boolean&lt;/property-class&gt;
  543. &lt;property-extension&gt;
  544. &lt;allow-run-time-binding&gt;true&lt;/allow-run-time-binding&gt;
  545. &lt;designer-extension&gt;
  546. &lt;category&gt;display&lt;/category&gt;
  547. &lt;/designer-extension&gt;
  548. &lt;/property-extension&gt;
  549. &lt;/property&gt;
  550.  
  551. &lt;component-extension&gt;
  552.  
  553. &lt;base-component-type&gt;com.ibm.xsp.Panel
  554. &lt;/base-component-type&gt;
  555.  
  556. &lt;component-family&gt;javax.faces.Panel
  557. &lt;/component-family&gt;
  558.  
  559. &lt;tag-name&gt;container&lt;/tag-name&gt;
  560.  
  561. &lt;designer-extension&gt;
  562. &lt;in-palette&gt;true&lt;/in-palette&gt;
  563. &lt;category&gt;Gregorbyte Library&lt;/category&gt;
  564. &lt;/designer-extension&gt;
  565.  
  566. &lt;/component-extension&gt;
  567.  
  568. &lt;/component&gt;
  569.  
  570. &lt;/faces-config&gt;</pre><p></p>
  571. <h2>Installing</h2>
  572. <p>If you would like to use these controls, you have 2 choices!</p>
  573. <ol>
  574. <li>Install the <a href="https://github.com/camac/GregorbyteXspLibrary/releases" target="_blank">latest version of my GregorbyteXspLibrary</a> to your Designer Client and Domino Server using the same method as Extension Library Installation</li>
  575. <li>Scavenge the necessary files from my Github repository and include it your own NSF</li>
  576. </ol>
  577. <p>To Scavenge the necessary files from my <a href="https://github.com/camac/GregorbyteXspLibrary" target="_blank">Github Repo</a>:</p>
  578. <ul>
  579. <li>You will need to place these java files in your java design elements:
  580. <ul>
  581. <li>/com.gregorbyte.xsp.controls/src/com/gregorbyte/xsp/component/bootstrap/UIColumn.java</li>
  582. <li>/com.gregorbyte.xsp.controls/src/com/gregorbyte/xsp/component/bootstrap/UIRow.java</li>
  583. <li>/com.gregorbyte.xsp.controls/src/com/gregorbyte/xsp/component/bootstrap/UIContainer.java</li>
  584. <li>/com.gregorbyte.xsp.core/src/com/gregorbyte/xsp/util/GregorbyteUtil.java</li>
  585. </ul>
  586. </li>
  587. <li>You will need to place this xsp-config file in the WEB-INF directory
  588. <ul>
  589. <li>/com.gregorbyte.xsp.controls/src/com/gregorbyte/xsp/config/gregorbyte-bootstrap.xsp-config</li>
  590. </ul>
  591. </li>
  592. </ul>
  593. <h1>Conclusion</h1>
  594. <p>You can easily get the job done without these controls, but so far I have really enjoyed having them in my palette.</p>
  595. <p>I&#8217;ve only just started using these controls, so I expect to improve on them. Maybe by adding the &#8216;small&#8217;, &#8216;medium&#8217;, &#8216;large&#8217;, &#8216;extraLarge&#8217; properties to the columns (but for now I can use styleClass=&#8221;col-sm-4&#8243; etc.)</p>
  596. <p>I&#8217;m also keen to take a look at other parts of the bootfaces project and see if any of the ideas might be effective in Xpages.</p>
  597. <p>If you end up using these please let me know! I&#8217;d love to hear any suggestions and as always if any problems then please leave a comment or post an issue on my GregorbyteXspLibrary project.</p>
  598. <p>&nbsp;</p>
  599. <h3></h3>
  600. ]]></content:encoded>
  601. <wfw:commentRss>http://camerongregor.com/2017/03/22/auto-width-bootstrap-column-xpages-controls/feed/</wfw:commentRss>
  602. <slash:comments>0</slash:comments>
  603. </item>
  604. <item>
  605. <title>Swiper FP8 Version Beta Release</title>
  606. <link>http://camerongregor.com/2017/03/16/swiper-fp8-version-beta-release/</link>
  607. <comments>http://camerongregor.com/2017/03/16/swiper-fp8-version-beta-release/#comments</comments>
  608. <pubDate>Thu, 16 Mar 2017 12:50:18 +0000</pubDate>
  609. <dc:creator><![CDATA[camerongregor]]></dc:creator>
  610. <category><![CDATA[Software Development]]></category>
  611. <category><![CDATA[designer]]></category>
  612. <category><![CDATA[sourcecontrol]]></category>
  613. <category><![CDATA[swiper]]></category>
  614.  
  615. <guid isPermaLink="false">http://camerongregor.com/?p=469</guid>
  616. <description><![CDATA[Last week I released the &#8216;alpha&#8217; version of Swiper which was untested on FP8 but presumed to be ok. So far I have only had good reports from the pioneers who have gone ahead&#46;&#46;&#46;]]></description>
  617. <content:encoded><![CDATA[<p>Last week <a href="http://camerongregor.com/2017/03/08/swiper-fp8-rollout/" target="_blank">I released the &#8216;alpha&#8217; version of Swiper</a> which was untested on FP8 but presumed to be ok. So far I have only had good reports from the pioneers who have gone ahead and installed FP8 + the alpha version.</p>
  618. <p>I have since managed to ugrade my home office setup to FP8 which unfortunately has broken my ability to launch designer from eclipse but I am seeking some advice on fixing this up.</p>
  619. <p>In the meantime I have to test the slow way of building plugins, import plugins, restart &#8230; ugh.</p>
  620. <p>Anyway, I persist because I want to give the people what they want! I have prepared another release with some requests received last week. This will most likely be the final 2.0.0 release but I am just releasing as beta for now. You can <a href="https://github.com/camac/Swiper/releases/tag/v2.0.0-beta" target="_blank">download the v2.0.0 Beta release from the github repo right now</a>! I&#8217;d love to hear if the requested features are also useful for you.</p>
  621. <p>Note: for the toolbar buttons to enable, you must click on the Project Header in the left pane</p>
  622. <h3><strong>&#8216;Refresh ODP and then Sync&#8217; button</strong></h3>
  623. <p>requested by Tinus Riyanto</p>
  624. <p><img class="aligncenter size-full wp-image-470" src="http://camerongregor.com/wp-content/uploads/2017/03/SwiperRefreshThenSyncButton.jpg" alt="" width="434" height="143" srcset="http://camerongregor.com/wp-content/uploads/2017/03/SwiperRefreshThenSyncButton.jpg 434w, http://camerongregor.com/wp-content/uploads/2017/03/SwiperRefreshThenSyncButton-300x99.jpg 300w" sizes="(max-width: 434px) 100vw, 434px" /></p>
  625. <p>Tinus&#8217; setup is to have Build Automatically off, Refresh Automatically off (I assume) and also &#8216;Auto Import&#8217; off.<br />
  626. This means that to for sync, he would need to find the ODP and right-click refresh, and then run a sync</p>
  627. <p>Instead, he can now just click &#8216;Refresh ODP and then Sync&#8217; button!</p>
  628. <p>I spent about 20 minutes trying to figure out what Icon could represent &#8216;Refresh and Sync&#8217; without confusing it with the other icons. I gave up and chose the 8-ball :p</p>
  629. <p>For bonus points I threw in a &#8216;Refresh ODP&#8217; button which just refreshes the ODP.</p>
  630. <p><img class="aligncenter size-full wp-image-471" src="http://camerongregor.com/wp-content/uploads/2017/03/SwiperRefreshODPButton.jpg" alt="" width="434" height="144" srcset="http://camerongregor.com/wp-content/uploads/2017/03/SwiperRefreshODPButton.jpg 434w, http://camerongregor.com/wp-content/uploads/2017/03/SwiperRefreshODPButton-300x100.jpg 300w" sizes="(max-width: 434px) 100vw, 434px" /></p>
  631. <h3><strong>&#8216;Launch ODP Folder in System Explorer&#8217;</strong></h3>
  632. <p>requested by me!</p>
  633. <p>A few times I just want to open the ODP in the System Explorer (windows Explorer) so I added this button to quickly launch it without having to navigate to package explorer etc.</p>
  634. <p><img class="aligncenter size-full wp-image-472" src="http://camerongregor.com/wp-content/uploads/2017/03/SwiperLaunchODPButton.jpg" alt="" width="434" height="144" srcset="http://camerongregor.com/wp-content/uploads/2017/03/SwiperLaunchODPButton.jpg 434w, http://camerongregor.com/wp-content/uploads/2017/03/SwiperLaunchODPButton-300x100.jpg 300w" sizes="(max-width: 434px) 100vw, 434px" /></p>
  635. <p>&nbsp;</p>
  636. <h3>&#8216;Enable Swiper for All projects&#8217; preference option</h3>
  637. <p>Requested by Patrick Kwinten</p>
  638. <p><img class="aligncenter size-full wp-image-474" src="http://camerongregor.com/wp-content/uploads/2017/03/SwiperEnableForAll-1.jpg" alt="" width="518" height="220" srcset="http://camerongregor.com/wp-content/uploads/2017/03/SwiperEnableForAll-1.jpg 518w, http://camerongregor.com/wp-content/uploads/2017/03/SwiperEnableForAll-1-300x127.jpg 300w" sizes="(max-width: 518px) 100vw, 518px" /></p>
  639. <p>Patrick wants to &#8216;Fire and Forget&#8217; on a Headless Build setup so that all projects will definitely have swiper enabled when the Headless build runs.</p>
  640. <h2>Next step</h2>
  641. <p>As long as I hear everything is good, I will publish the final version 2.0.0 to OpenNTF next week.</p>
  642. <p>As always happy to hear any further suggestions that may make your life with source-control in Domino Designer easier.</p>
  643. ]]></content:encoded>
  644. <wfw:commentRss>http://camerongregor.com/2017/03/16/swiper-fp8-version-beta-release/feed/</wfw:commentRss>
  645. <slash:comments>6</slash:comments>
  646. </item>
  647. <item>
  648. <title>Swiper FP8 Integration Rollout</title>
  649. <link>http://camerongregor.com/2017/03/08/swiper-fp8-rollout/</link>
  650. <comments>http://camerongregor.com/2017/03/08/swiper-fp8-rollout/#comments</comments>
  651. <pubDate>Wed, 08 Mar 2017 12:43:10 +0000</pubDate>
  652. <dc:creator><![CDATA[camerongregor]]></dc:creator>
  653. <category><![CDATA[Software Development]]></category>
  654. <category><![CDATA[designer]]></category>
  655. <category><![CDATA[sourcecontrol]]></category>
  656. <category><![CDATA[swiper]]></category>
  657.  
  658. <guid isPermaLink="false">http://camerongregor.com/?p=461</guid>
  659. <description><![CDATA[Notes Domino 9.0.1 FP8 is finally here and as far as I know (I have yet to download it) it includes the necessary changes which will allow Swiper to swipe whatever it wants, whenever it wants,&#46;&#46;&#46;]]></description>
  660. <content:encoded><![CDATA[<p>Notes Domino 9.0.1 FP8 is finally here and as far as I know (I have yet to download it) it includes the necessary changes which will allow Swiper to swipe whatever it wants, whenever it wants, which is good news for people who don&#8217;t like to have &#8216;Build Automatically&#8217; turned on.</p>
  661. <p>&nbsp;</p>
  662. <h3><strong>Plan of Attack for release of Swiper version 2.0.0</strong></h3>
  663. <p>So, I haven&#8217;t actually downloaded FP8 yet,  so I can&#8217;t say for sure that the updated version works perfectly. Here is a bit of background, I had been working with IBM team to make sure that the necessary changes would be sufficient. I received an updated version of the &#8216;team&#8217; plugin and have tested my POC successfully against that updated plugin, but I haven&#8217;t tested it fully against the full new FP8 because, as previously mentioned, I haven&#8217;t downloaded it yet.</p>
  664. <p>So the plan is: I am putting a <a href="https://github.com/camac/Swiper/releases/tag/v2.0.0-alpha">pre-release of Swiper v2.0.0</a> up on the <a href="https://github.com/camac/Swiper">Swiper Github repository</a>.</p>
  665. <blockquote><p>If you have updated to FP8 and would like to test the new version I would very much appreciated it!</p></blockquote>
  666. <p>Here are the steps to install and ugrade:</p>
  667. <ul>
  668. <li>Download <a href="https://github.com/camac/Swiper/releases/tag/v2.0.0-alpha">Swiper v2.0.0-alpha release</a> from Github and install to designer</li>
  669. <li>&#8216;Remove Swiper&#8217; and then &#8216;Add Swiper&#8217; from any projects currently using swiper<br />
  670. Basically, the &#8216;remove&#8217; will remove the old builders that are no longer needed, then &#8216;add&#8217; will re-enable the new version.</li>
  671. <li>Check that it is swiping your metadata whenever you sync!</li>
  672. </ul>
  673. <p>I will also upgrade my Notes over the next week and test. Once I have heard back from a few people that it is working well, then I will prepare the final release and update the OpenNTF Project page.</p>
  674. <h3><strong>Bonus Feature!</strong></h3>
  675. <p>I added a new feature: there is a &#8216;Sync&#8217; toolbar button to perform a sync on the currently active project. Give it a try and let me know if useful.</p>
  676. <p><img class="aligncenter size-full wp-image-462" src="http://camerongregor.com/wp-content/uploads/2017/03/SwiperSyncButton.jpg" alt="" width="146" height="59" /></p>
  677. <h3><strong>Background of the Reason for new Integration point</strong></h3>
  678. <p>If you are curious about why there was a change necessary to allow Swiper to perform more efficiently, here is a summary:</p>
  679. <p>Swiper version 1 worked like this:</p>
  680. <ul>
  681. <li>Designer has a &#8216;Builder&#8217; which triggers the sync process of Design Elements to Disk</li>
  682. <li>Swiper version 1 sandwiched this Builder with 2 builders of it&#8217;s own, a Pre and Post Sync builder.</li>
  683. <li>The Pre sync builder saved some info about files that might be exported</li>
  684. <li>The Post sync builder used Pre Sync builder to figure out if files were exported, if they were then it filtered them</li>
  685. </ul>
  686. <p>The trouble with this, is that there is more than one way for the Sync process to be triggered. If it was triggered on &#8216;project open&#8217; event or manually with the right-click menu, then the Swiper builders would not run.</p>
  687. <p>IBM have now made a modification to provide a &#8216;SyncListener&#8217; extension point, so that a class can be registered to listen to import / export / rename / delete events, and has the opportunity to run some code when those events fire.</p>
  688. <p>This allows swiper to run whenever there is a sync operation, regardless of when it was called.</p>
  689. <p>So Swiper version 2 simply listens for the events and then acts accordingly whenever they happen.</p>
  690. <h4><strong>Other uses for SyncListener</strong></h4>
  691. <p>I am also curious if there are other options to utilise this feature to correct DXL Import export errors. One example is to do with agent scheduling for all-day agents. A custom Scheduling metadata file could be created and used to save the agent scheduling info on export, and then configure the agent after import.</p>
  692. <h3><strong>Feedback welcome</strong></h3>
  693. <p>Let me know how you go, you can report bugs either on <a href="https://www.openntf.org/main.nsf/project.xsp?r=project/Swiper/summary">OpenNTF Swiper Project</a> or the <a href="https://github.com/camac/Swiper/issues">Github Repository Issues</a></p>
  694. <p>Or maybe you just want to say it is all working well! It is nice to hear when people say thanks so if it is helping you please don&#8217;t be shy! I do get some nice comments from time to time and it is a great ego boost.</p>
  695. <p>When I first learned of Source Control Enablement in Designer, I was excited and then disappointed.</p>
  696. <p>It was clearly not a workable solution if you needed to do any sort of branching and merging, which is core to good source control.</p>
  697. <p>I was determined to get it working property. The initial DORA solution, took me about 2-3 months in my spare time to problem solve, develop and prepare so it was robust enough to share with others. It was workable but still a bit too fiddly. Later I decided that it would be cleaner to have it integrated into Designer, and a further month or two of development to get it ready. It was much more robust to install, and almost perfect.</p>
  698. <p>This latest version should complete the puzzle, and hopefully there will be no more major developments needed!</p>
  699. <p>I cannot imagine being able to manage the projects I have been working on over the past few years if I did not have source control, so I hope that my efforts have also enable you to work collaboratively with others, and also manage your own local branches as well!</p>
  700. ]]></content:encoded>
  701. <wfw:commentRss>http://camerongregor.com/2017/03/08/swiper-fp8-rollout/feed/</wfw:commentRss>
  702. <slash:comments>13</slash:comments>
  703. </item>
  704. <item>
  705. <title>Markdown XPages UIControl</title>
  706. <link>http://camerongregor.com/2017/03/05/markdown-xpages-uicontrol/</link>
  707. <comments>http://camerongregor.com/2017/03/05/markdown-xpages-uicontrol/#comments</comments>
  708. <pubDate>Sun, 05 Mar 2017 11:44:24 +0000</pubDate>
  709. <dc:creator><![CDATA[camerongregor]]></dc:creator>
  710. <category><![CDATA[Software Development]]></category>
  711. <category><![CDATA[control]]></category>
  712. <category><![CDATA[markdown]]></category>
  713. <category><![CDATA[xpages]]></category>
  714.  
  715. <guid isPermaLink="false">http://camerongregor.com/?p=444</guid>
  716. <description><![CDATA[Often when I&#8217;m designing an xpage, there might be a section of the page in which I want to explain some instructions to the user. Some options here are to: write the Instructions using html&#46;&#46;&#46;]]></description>
  717. <content:encoded><![CDATA[<p>Often when I&#8217;m designing an xpage, there might be a section of the page in which I want to explain some instructions to the user. Some options here are to:</p>
  718. <ol>
  719. <li>write the Instructions using html and embed directly in the xpage markup</li>
  720. <li>write the Instructions directly in the design pane and format using designer&#8217;s ui e.g. bold, color, size etc</li>
  721. <li>use some native xpage controls to achieve the desired result.</li>
  722. <li>Write the instructions in a richtext field on a notes document that is loaded to display the info.</li>
  723. </ol>
  724. <p>Option 1 works good but is annoying to type html opening and closing all those tags. Option 2 is quick to use but creates ugly html with hardcoded styles / colours etc. Option 3 is doable but still a little clunky, Option 4 is not a great move because it is very hard to include the instructions within source control.</p>
  725. <h3>A better option needed</h3>
  726. <p><a href="https://en.wikipedia.org/wiki/Lightweight_markup_language">Lightweight markup languages</a> are a great way to let formatting &#8216;get out of the way&#8217; and just let you focus on writing the content. There are many lightweight languages to choose from. <a href="http://daringfireball.net/projects/markdown/">Markdown </a>is a popular one so I thought that would be great to be able to quickly write some markdown and let the formatting take care of itself.</p>
  727. <p>If you aren&#8217;t familiar with the concept, it is basically a system of writing some plaintext content that can automatically be converted to html. For example the following text is plain text but easy to see the intended structure:</p><pre class="crayon-plain-tag">This is a heading
  728. =================
  729.  
  730. This is a paragraph
  731.  
  732. * this is a list item
  733. * this is another item</pre><p>This will processed by Markdown into the following html</p><pre class="crayon-plain-tag">&lt;h1&gt;This is a Heading&lt;/h1&gt;
  734. &lt;p&gt;This is a paragraph&lt;/p&gt;
  735. &lt;ul&gt;
  736. &lt;li&gt;this is a list item&lt;/li&gt;
  737. &lt;li&gt;this is another item&lt;/li&gt;
  738. &lt;/ul&gt;</pre><p></p>
  739. <h3>Side note: Asciidoctor is good too</h3>
  740. <p>I have also created a similar control for AsciiDoctor which is another lightweight format. I actually prefer to use Asciidoctor now, however since Markdown seems to be more common I thought I would share the markdown control first.</p>
  741. <p>If you are interested in AsciiDoctor control let me know. It is a bit more intense though because it starts up a JRuby instance and maybe that is not something you&#8217;d like to do (because AsciiDoctor is ruby based).</p>
  742. <h2>Overview</h2>
  743. <p>A few years ago <a href="https://www.ovalbusinesssolutions.co.uk/single-post/2015/04/29/Practical-text-formatting-with-markdown">Martin Rolph from Oval UK shared a solution</a> of a Markdown Custom Control. You can find the project on github at <a href="https://github.com/OvalUK/XPagesDemos">OvalUK/XPagesDemos</a>. The solution used <a href="https://code.google.com/archive/p/markdown4j/">markdown4j </a>which is a java-based markdown processor. I decided to build on Martin&#8217;s work and convert it from a custom control into an XspLibrary Control with a few extra options of where to retrieve the markdown from.</p>
  744. <p>The markdown control allows you bind to a dynamic data source such as a NotesDocument / scoped variable etc. but also provides some options to load the source text from some static locations. You can have the source text:</p>
  745. <ul>
  746. <li>Bound to a String Field in a Datasource or Scope variable</li>
  747. <li>Supplied from a text file in the WEB-INF directory (read only)</li>
  748. <li>Supplied from a text file from within a plugin (read only)</li>
  749. <li>Supplied from a File in the &#8216;Resources&#8217; Design Element (read only)</li>
  750. <li>Written directly within the control&#8217;s start and end tags (passthrough text) but this has some gotchas. (read only)</li>
  751. </ul>
  752. <p>The control is an extension of the normal &#8216;inputTextArea&#8217; control. When the control is in edit mode it just works exactly like a plain text area.</p>
  753. <p>In Read-only mode, the supplied plaintext will be converted to html for display using the Markdown Processor.</p>
  754. <h3>Simple Example</h3>
  755. <p>In this simple example, I am binding the text to a viewScope variable &#8216;sampleText&#8217;<br />
  756. I have 2 markdown controls, one on the left in edit mode, and one on the right in read mode, showing a live preview of the markup using the onKeyUp event to refresh the preview control. Here is the markup for the page</p><pre class="crayon-plain-tag">&lt;h2&gt;Live Preview example&lt;/h2&gt;
  757.  
  758. &lt;xp:table id="tablePreview" style="width:800px;margin-bottom:50.0px;margin-top:10.0px"&gt;
  759. &lt;xp:tr&gt;
  760. &lt;xp:td style="width:50%" valign="top"&gt;
  761. &lt;gb:markdown id="markdown2" value="#{viewScope.sampleText}"&gt;
  762. &lt;xp:eventHandler event="onkeyup" submit="true" refreshMode="partial" refreshId="markdown3" execMode="partial" execId="tablePreview"
  763. disableValidators="true"&gt;
  764. &lt;/xp:eventHandler&gt;
  765. &lt;/gb:markdown&gt;
  766. &lt;/xp:td&gt;
  767. &lt;xp:td valign="top"&gt;
  768. &lt;gb:markdown id="markdown3" value="#{viewScope.sampleText}" readonly="true"&gt;&lt;/gb:markdown&gt;
  769. &lt;/xp:td&gt;
  770. &lt;/xp:tr&gt;
  771. &lt;/xp:table&gt;</pre><p>And here is an example of how it looks</p>
  772. <p><img class="aligncenter size-full wp-image-446" src="http://camerongregor.com/wp-content/uploads/2017/03/MarkdownLivePreview.jpg" alt="" width="693" height="234" srcset="http://camerongregor.com/wp-content/uploads/2017/03/MarkdownLivePreview.jpg 693w, http://camerongregor.com/wp-content/uploads/2017/03/MarkdownLivePreview-300x101.jpg 300w" sizes="(max-width: 693px) 100vw, 693px" /></p>
  773. <h2>Examples of the different methods of supplying the text</h2>
  774. <p>You should be able to find the control in the Controls palette under Gregorbyte Library. Drag it onto the page.</p>
  775. <p><img class="aligncenter size-full wp-image-457" src="http://camerongregor.com/wp-content/uploads/2017/03/MarkdownPalette.jpg" alt="" width="213" height="52" /></p>
  776. <h3>Bound to a Datasource / Managed Bean</h3>
  777. <p>You can bind it to any text field just like you would normally. In this example it is bound to a document and in edit mode acts just like a normal text area.</p><pre class="crayon-plain-tag">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
  778. &lt;xp:view xmlns:xp="http://www.ibm.com/xsp/core" xmlns:gb="http://www.gregorbyte.com/xsp/"&gt;
  779.  
  780. &lt;xp:this.data&gt;
  781. &lt;xp:dominoDocument var="document1" formName="Markdown"&gt;&lt;/xp:dominoDocument&gt;
  782. &lt;/xp:this.data&gt;
  783.  
  784. &lt;xp:button value="Save" id="button1"&gt;
  785. &lt;xp:eventHandler event="onclick" submit="true" refreshMode="complete"&gt;
  786. &lt;xp:this.action&gt;
  787. &lt;xp:saveDocument&gt;&lt;/xp:saveDocument&gt;
  788. &lt;/xp:this.action&gt;
  789. &lt;/xp:eventHandler&gt;
  790. &lt;/xp:button&gt;
  791.  
  792. &lt;gb:markdown id="markdown1" value="#{document1.markdownText}"&gt;&lt;/gb:markdown&gt;
  793.  
  794. &lt;/xp:view&gt;</pre><p>Then in edit mode:</p>
  795. <p><img class="aligncenter size-full wp-image-452" src="http://camerongregor.com/wp-content/uploads/2017/03/MarkdownDSEntry.jpg" alt="" width="381" height="259" srcset="http://camerongregor.com/wp-content/uploads/2017/03/MarkdownDSEntry.jpg 381w, http://camerongregor.com/wp-content/uploads/2017/03/MarkdownDSEntry-300x204.jpg 300w" sizes="(max-width: 381px) 100vw, 381px" /></p>
  796. <p>In Read only mode:</p>
  797. <p><img class="aligncenter size-full wp-image-453" src="http://camerongregor.com/wp-content/uploads/2017/03/MarkdownDSRead.jpg" alt="" width="305" height="124" srcset="http://camerongregor.com/wp-content/uploads/2017/03/MarkdownDSRead.jpg 305w, http://camerongregor.com/wp-content/uploads/2017/03/MarkdownDSRead-300x122.jpg 300w" sizes="(max-width: 305px) 100vw, 305px" /></p>
  798. <h3>Supplied from WEB-INF</h3>
  799. <p>To read a file from WEB-INF the &#8216;value&#8217; should be the path to the file</p>
  800. <ul>
  801. <li>It must start with &#8216;/WEB-INF&#8217;</li>
  802. <li>It must finish with &#8216;.md&#8217;</li>
  803. <li>readonly property should be &#8216;true&#8217;</li>
  804. </ul>
  805. <p>e.g.</p><pre class="crayon-plain-tag">&lt;!-- Read from WEB-INF --&gt;
  806. &lt;gb:markdown id="markdown4" readonly="true" value="/WEB-INF/markdown/Sample.md"&gt;&lt;/gb:markdown&gt;</pre><p><img class="aligncenter size-full wp-image-447" src="http://camerongregor.com/wp-content/uploads/2017/03/MarkdownSampleWebInf.jpg" alt="" width="265" height="245" /></p>
  807. <h3>Supplied from a File in the Resources Design element</h3>
  808. <p>To supply the text from a File in the Resources Design element, give your file should the .md extension, and then supply &#8216;value&#8217; as &#8216;/&lt;yourfilename&gt;.md&#8217;, also set readonly=&#8221;true&#8221; e.g.</p><pre class="crayon-plain-tag">&lt;!-- Read from the File Resources --&gt;
  809. &lt;gb:markdown id="markdown1" readonly="true" value="/FileResource.md"&gt;&lt;/gb:markdown&gt;</pre><p><img class="aligncenter size-full wp-image-448" src="http://camerongregor.com/wp-content/uploads/2017/03/MarkdownSampleFileResource.jpg" alt="" width="505" height="269" srcset="http://camerongregor.com/wp-content/uploads/2017/03/MarkdownSampleFileResource.jpg 505w, http://camerongregor.com/wp-content/uploads/2017/03/MarkdownSampleFileResource-300x160.jpg 300w" sizes="(max-width: 505px) 100vw, 505px" /></p>
  810. <h3>Supplied from a TextFile in a plugin</h3>
  811. <p>To supply the text from a file in a plugin, make sure your file has .md extension. Right click on the file in eclipse and &#8216;Copy Qualified Name&#8217;. Paste this as the &#8216;value&#8217; and set readonly = true.<br />
  812. You may run into some trouble if your file is not included in the build process, or it is built to a different location than it is in the source code, but basically the format should be &#8216;/com.your.plugin/your/classpath/to/resource/file.md&#8217;</p><pre class="crayon-plain-tag">&lt;!-- Read from A plugin --&gt;
  813. &lt;gb:markdown id="markdown6" readonly="true" value="/com.gregorbyte.xsp/resources/sample/Markdown.md"&gt;&lt;/gb:markdown&gt;</pre><p><img class="aligncenter size-full wp-image-449" src="http://camerongregor.com/wp-content/uploads/2017/03/MarkdownSamplePluginSource.jpg" alt="" width="774" height="287" srcset="http://camerongregor.com/wp-content/uploads/2017/03/MarkdownSamplePluginSource.jpg 774w, http://camerongregor.com/wp-content/uploads/2017/03/MarkdownSamplePluginSource-300x111.jpg 300w, http://camerongregor.com/wp-content/uploads/2017/03/MarkdownSamplePluginSource-768x285.jpg 768w" sizes="(max-width: 774px) 100vw, 774px" /></p>
  814. <h3>Written directly within start and end tag</h3>
  815. <p>This is a quick and dirty method but it is a bit buggy. If you sometimes format your xsp markup with Ctrl + Shift + F then it will ruin your markdown with indents etc. The markdown text must be placed flush with the margin of the code editor</p><pre class="crayon-plain-tag">&lt;gb:markdown id="markdown5" readonly="true"&gt;
  816. This text is directly within the tags
  817. =====================================
  818.  
  819. It has some drawbacks though
  820.  
  821. * The text must be lined up directly against the margin (No indents)
  822. * If you hit 'Ctrl + Shift + F' to format your xsp markup, it will indent the text and ruin everything
  823. * You cannot embed html tags in the text
  824.  
  825. These bugs could be fixed but I just haven't got to it yet
  826. &lt;/gb:markdown&gt;</pre><p></p>
  827. <h2>Installation</h2>
  828. <p>To install you do one of the following:</p>
  829. <ul>
  830. <li>install my GregorbyteXspLibrary to your designer + server</li>
  831. <li>Scavenge necessary files from the Github repo</li>
  832. </ul>
  833. <h3>Method 1: Install my library</h3>
  834. <p>Go to the <a href="https://github.com/camac/GregorbyteXspLibrary/releases">latest releases of camac/GregorbyteXspLibrary</a>, download the zip file and install the update site using your favourite methods (same method as you would install IBM Extension Library)</p>
  835. <h3>Method 2: Scavenge Necessary files from github repo</h3>
  836. <p>You will need These java files</p>
  837. <ul>
  838. <li>/com.gregorbyte.xsp.controls/src/com/gregorbyte/xsp/component/markdown/UIMarkdown.java</li>
  839. <li>/com.gregorbyte.xsp.controls/src/com/gregorbyte/xsp/renderkit/markdown/MarkdownRenderer.java</li>
  840. </ul>
  841. <p>You will need this JAR</p>
  842. <ul>
  843. <li>/com.gregorbyte.lib.markdown4j/lib/markdown4j-2.2.jar</li>
  844. </ul>
  845. <p>You will need this xsp-config (place in your WEB-INF</p>
  846. <ul>
  847. <li>/com.gregorbyte.xsp.controls/src/com/gregorbyte/xsp/config/gregorbyte-markdown.xsp-config</li>
  848. </ul>
  849. <p>You will need to include the contents of this file in your WEB-INF/faces-config.xml file</p>
  850. <ul>
  851. <li>/com.gregorbyte.xsp.controls/src/com/gregorbyte/xsp/config/gregorbyte-markdown-faces-config.xml</li>
  852. </ul>
  853. <p>I am pretty sure that is all that is needed, let me know if you have any trouble.</p>
  854. <h2>Conclusion</h2>
  855. <p>Once again thanks to Martin Rolph for providing the markdown custom control in the first place, I have simply added a few more ways to supply the source and wrapped it in a control.</p>
  856. <p>I&#8217;d love to hear what you think if this is useful to you or not. Personally I have been using it mainly to supply markdown from within a plugin. Mainly help/instructions/code examples in our demo database.</p>
  857. ]]></content:encoded>
  858. <wfw:commentRss>http://camerongregor.com/2017/03/05/markdown-xpages-uicontrol/feed/</wfw:commentRss>
  859. <slash:comments>2</slash:comments>
  860. </item>
  861. <item>
  862. <title>TextDiff XPages control &#8211; For visual comparison of text</title>
  863. <link>http://camerongregor.com/2017/02/28/textdiff-xpages-control-for-visual-comparison-of-text/</link>
  864. <comments>http://camerongregor.com/2017/02/28/textdiff-xpages-control-for-visual-comparison-of-text/#comments</comments>
  865. <pubDate>Tue, 28 Feb 2017 12:00:19 +0000</pubDate>
  866. <dc:creator><![CDATA[camerongregor]]></dc:creator>
  867. <category><![CDATA[Software Development]]></category>
  868. <category><![CDATA[control]]></category>
  869. <category><![CDATA[xpages]]></category>
  870.  
  871. <guid isPermaLink="false">http://camerongregor.com/2017/02/28/textdiff-xpages-control-for-visual-comparison-of-text/</guid>
  872. <description><![CDATA[A few years back I stumbled across Google&#8217;s diff-match-patch project which provides some handy algorithms for text manipulation. At the time of discovery I was doing &#8216;classic&#8217; notes development. Although I probably could have&#46;&#46;&#46;]]></description>
  873. <content:encoded><![CDATA[<p>A few years back I stumbled across <a href="https://code.google.com/p/google-diff-match-patch/" target="_blank">Google&#8217;s diff-match-patch project</a> which provides some handy algorithms for text manipulation. At the time of discovery I was doing &#8216;classic&#8217; notes development. Although I probably could have implemented something that worked in lotuscript with RichText or Mime, it wasn&#8217;t a priority at the time and I never bothered.</p>
  874. <p>Since then, I have been doing mainly XPages, and now that I have been also doing a bit of XPages Control development. I was mainly interested in the &#8216;diff&#8217; algorithm so I decided the time was right to turn the diff algorithm into a useful XPages control, which takes 2 input strings and displays a visual difference between them.</p>
  875. <p>The diff-match-patch algorithm / project is licenced under Apache Licence 2.0 so it is the same licence as most OpenNTF projects.</p>
  876. <h3>Overview</h3>
  877. <p>Basically the control takes a &#8216;from&#8217; and &#8216;to&#8217; input, and displays the visual diff. I guess it is best to show the example!</p>
  878. <p>Here is some From and To Input that we will use for our examples. I have bound these textareas to viewScope &#8216;fromText&#8217; and &#8216;toText&#8217; but they could be bound to whatever text field you like</p>
  879. <p><img class="aligncenter size-full wp-image-435" src="http://camerongregor.com/wp-content/uploads/2017/02/TextDiffExampleInput.jpg" alt="" width="585" height="114" srcset="http://camerongregor.com/wp-content/uploads/2017/02/TextDiffExampleInput.jpg 585w, http://camerongregor.com/wp-content/uploads/2017/02/TextDiffExampleInput-300x58.jpg 300w" sizes="(max-width: 585px) 100vw, 585px" /></p>
  880. <p>So then we will use the text diff like so:</p><pre class="crayon-plain-tag">&lt;gb:textDiff id="textDiff1" from="#{viewScope.fromText}" to="#{viewScope.toText}" disableTheme="true"&gt;
  881. &lt;/gb:textDiff&gt;</pre><p>Note the following picture is from my demo page so the actual output from the textdiff control is only what is inside the black box.</p>
  882. <p><img class="aligncenter size-full wp-image-436" src="http://camerongregor.com/wp-content/uploads/2017/02/TextDiffDefault.jpg" alt="" width="524" height="103" srcset="http://camerongregor.com/wp-content/uploads/2017/02/TextDiffDefault.jpg 524w, http://camerongregor.com/wp-content/uploads/2017/02/TextDiffDefault-300x59.jpg 300w" sizes="(max-width: 524px) 100vw, 524px" /></p>
  883. <h3>Algorithm parameters</h3>
  884. <p>For a good explanation of the algorithm parameters, I suggest you have a look at the google diff-match-patch webpage. But here is my best explanation!</p>
  885. <ul>
  886. <li><strong>cleanup</strong> &#8211; whether to perform some sort of cleanup to produce some more human readable results. I find &#8216;semantic&#8217; is a good option</li>
  887. <li><strong>edit cost</strong> &#8211; only used for &#8216;efficiency&#8217; cleanup</li>
  888. <li><strong>Timeout</strong> &#8211; don&#8217;t spend longer than this amount of seconds</li>
  889. </ul>
  890. <p>Here is an example of output after changing the &#8216;cleanup&#8217; option to &#8216;semantic&#8217;. Note that it has lengthened the change from single words to a longer run of words</p>
  891. <p><img class="aligncenter size-full wp-image-437" src="http://camerongregor.com/wp-content/uploads/2017/02/TextDiffOptions.jpg" alt="" width="628" height="134" srcset="http://camerongregor.com/wp-content/uploads/2017/02/TextDiffOptions.jpg 628w, http://camerongregor.com/wp-content/uploads/2017/02/TextDiffOptions-300x64.jpg 300w" sizes="(max-width: 628px) 100vw, 628px" /></p>
  892. <h3>Styling</h3>
  893. <p>The control has style and styleClass properties for the insert, delete and equals operations. Basically the control outputs a bunch of &#8216;&lt;span&gt;&#8217;s for each chunk of the diff operation. These outputs are either an insertion, a deletion or &#8216;equals&#8217;, and they have associated &#8216;insertStyle&#8217; and &#8216;insertStyleClass&#8217; properties.</p>
  894. <h3>Themable</h3>
  895. <p>All the above properties are themable. You can use the &#8216;Text.Diff&#8217; control type to set properties for all textdiff controls or you can come up with a new ThemeId that can be applied as needed.</p>
  896. <p>Here is an example using the following theme properties with a custom ThemeId</p><pre class="crayon-plain-tag">&lt;control&gt;
  897. &lt;name&gt;Text.Diff.Funky&lt;/name&gt;
  898. &lt;property&gt;
  899. &lt;name&gt;style&lt;/name&gt;
  900. &lt;value&gt;background-color: black;&lt;/value&gt;
  901. &lt;/property&gt;
  902. &lt;property&gt;
  903. &lt;name&gt;insertStyle&lt;/name&gt;
  904. &lt;value&gt;color: aqua;&lt;/value&gt;
  905. &lt;/property&gt;
  906. &lt;property&gt;
  907. &lt;name&gt;equalStyle&lt;/name&gt;
  908. &lt;value&gt;color: lime;&lt;/value&gt;
  909. &lt;/property&gt;
  910. &lt;property&gt;
  911. &lt;name&gt;deleteStyle&lt;/name&gt;
  912. &lt;value&gt;color: pink;&lt;/value&gt;
  913. &lt;/property&gt;
  914. &lt;/control&gt;</pre><p><img class="aligncenter size-full wp-image-438" src="http://camerongregor.com/wp-content/uploads/2017/02/TextDiffTheme.jpg" alt="" width="697" height="65" srcset="http://camerongregor.com/wp-content/uploads/2017/02/TextDiffTheme.jpg 697w, http://camerongregor.com/wp-content/uploads/2017/02/TextDiffTheme-300x28.jpg 300w" sizes="(max-width: 697px) 100vw, 697px" /></p>
  915. <h3>Installation</h3>
  916. <p>You have 2 choices</p>
  917. <ul>
  918. <li>install my &#8216;Gregorbyte&#8217; extension library</li>
  919. <li>scavenge the necessary files from my github repo and install to your nsf</li>
  920. </ul>
  921. <h4>Install my Gregorbyte Extension Library</h4>
  922. <p>Go and get the <a href="https://github.com/camac/GregorbyteXspLibrary/releases" target="_blank">latest release</a> which should be an update site zip file.</p>
  923. <p>Install this to your domino designer, and also to your domino server using your favourite update site method.</p>
  924. <h4>To Scavenge the necessary files from my <a href="https://github.com/camac/GregorbyteXspLibrary" target="_blank">github repo</a></h4>
  925. <p>You&#8217;ll need to put these java files in your Java section of your NSF:</p>
  926. <ul>
  927. <li>/com.gregorbyte.xsp.controls/src/com/gregorbyte/xsp/component/UITextDiff.java</li>
  928. <li>/com.gregorbyte.xsp.controls/src/com/gregorbyte/xsp/renderkit/TextDiffRenderer.java</li>
  929. <li>/com.gregorbyte.xsp.controls/src/name/fraser/neil/plaintext/DiffMatchPatch.java</li>
  930. </ul>
  931. <p>And this xsp-config (place in your WebContent/WEB-INF directory in you NSF using package explorer)</p>
  932. <ul>
  933. <li>/com.gregorbyte.xsp.controls/src/com/gregorbyte/xsp/config/gregorbyte-textdiff.xsp-config</li>
  934. </ul>
  935. <p>and add this entry to your faces-config.xml</p><pre class="crayon-plain-tag">&lt;render-kit&gt;
  936. &lt;renderer&gt;
  937. &lt;component-family&gt;javax.faces.Output&lt;/component-family&gt;
  938. &lt;renderer-type&gt;com.gregorbyte.TextDiff&lt;/renderer-type&gt;
  939. &lt;renderer-class&gt;com.gregorbyte.xsp.renderkit.TextDiffRenderer&lt;/renderer-class&gt;
  940. &lt;/renderer&gt;
  941. &lt;/render-kit&gt;</pre><p>Let me know if it works or doesn&#8217;t work in case I forgot something. I haven&#8217;t used this control heavily in production yet so there may be a bug here and there, please report it if so!</p>
  942. <p>&nbsp;</p>
  943. ]]></content:encoded>
  944. <wfw:commentRss>http://camerongregor.com/2017/02/28/textdiff-xpages-control-for-visual-comparison-of-text/feed/</wfw:commentRss>
  945. <slash:comments>2</slash:comments>
  946. </item>
  947. <item>
  948. <title>XPages webmail &#8211; Using Mime Inspector to debug Mime</title>
  949. <link>http://camerongregor.com/2017/02/14/xpages-webmail-using-mime-inspector-to-debug-mime/</link>
  950. <comments>http://camerongregor.com/2017/02/14/xpages-webmail-using-mime-inspector-to-debug-mime/#comments</comments>
  951. <pubDate>Tue, 14 Feb 2017 11:12:21 +0000</pubDate>
  952. <dc:creator><![CDATA[camerongregor]]></dc:creator>
  953. <category><![CDATA[Software Development]]></category>
  954.  
  955. <guid isPermaLink="false">http://camerongregor.com/?p=320</guid>
  956. <description><![CDATA[In a previous post in this series I did a bit of an overview on how MIME works. We also did a little bit about how MIME works in XPages + Domino land. With&#46;&#46;&#46;]]></description>
  957. <content:encoded><![CDATA[<p>In a previous post in this series I did a bit of an overview on <a href="http://camerongregor.com/2016/04/21/webmail-ui-you-must-learn-about-mime/">how MIME works</a>. We also did a little bit about how MIME works in XPages + Domino land. With this knowledge in hand we can now start to analyse the different ways a &#8216;Pretty words, pictures and attachments&#8217; can be stored in the document.</p>
  958. <p>During development of the &#8216;XPages Webmail&#8217; interface, I encountered many problems which could only be solved by investigating the MIME content in detail.</p>
  959. <p>To help me do this, I developed a simple control (Mime Inspector) that I could use during debugging, which allowed me to see details of Mime Entities and the relation between them.</p>
  960. <p><strong>Overview</strong></p>
  961. <p>To use the MIME Inspector, I just bind it to a &#8220;rich text/Mime&#8221; field, and then view the output in the XPage. There are 2 control properties that can be control the level of detail</p>
  962. <ul>
  963. <li>showHeaders &#8211; will output the MimeHeader entries of each MimeEntity</li>
  964. <li>showContent &#8211; will output the actual content of each MimeEntity</li>
  965. </ul>
  966. <p>The output starts with a summary of the status of the DominoRichTextItem (which is a wrapper for the RichTextItem. This is less useful but can still be helpful to know in some situations.</p>
  967. <ul>
  968. <li>Are there Embedded Images that have not been saved yet</li>
  969. <li>are there attachments that are not saved yet</li>
  970. <li>is the wrapped field a MIME field (or otherwise still rich text)</li>
  971. <li>Is the backend item discarded</li>
  972. </ul>
  973. <p>Then the control will output the visual representation of the Mime Structure.</p>
  974. <p>It does this in a &#8216;Nested List&#8217; of MIMEEntities so that you can see sibling / parent / child relationship (which is very important when debugging MIME problems)</p>
  975. <p>Each Mime Entity will output:</p>
  976. <ul>
  977. <li>content type e.g. text/html</li>
  978. <li>encoding details</li>
  979. <li>mime headers (if set to do so with showHeaders)</li>
  980. <li>content (if set to do so with showContent)</li>
  981. </ul>
  982. <h3>Demonstration Video</h3>
  983. <p>Here is as short demonstration video of the control in action</p>
  984. <div class="video-container"><iframe width="500" height="281" src="https://www.youtube.com/embed/-WPqKAl3Y-E?feature=oembed&#038;wmode=opaque" frameborder="0" allowfullscreen></iframe></div>
  985. <h3></h3>
  986. <h3>Installation / usage</h3>
  987. <p>You can either install the GregorbyteXspLibrary and use from that, or you can rip out the necessary files and use within an NSF</p>
  988. <p>To do it via installing the library:</p>
  989. <ul>
  990. <li>Download the GregorbyteXspLibrary from the <a href="https://github.com/camac/GregorbyteXspLibrary/releases">project&#8217;s github releases page</a></li>
  991. <li>Install the library to your Domino Designer (same method as IBM ExtLib)</li>
  992. <li>Install the library to your Domino Server (same method as IBM ExtLib)</li>
  993. <li>Enable the library in your NSF (Xsp Properties page, &#8216;Page Generation&#8217; section choose &#8216;com.gregorbyte.xsp.library&#8217;</li>
  994. <li>Drag the &#8216;Mime Inspector&#8217; onto your page</li>
  995. </ul>
  996. <p>To do it the &#8216;non-library&#8217; way:</p>
  997. <ul>
  998. <li>Copy the <a href="https://github.com/camac/GregorbyteXspLibrary/blob/master/com.gregorbyte.xsp.core/src/com/gregorbyte/xsp/util/MimeUtil.java">MimeUtil </a>class and paste it in your java elements section (you will need to removeh/comment out all statements referencing the GregorbyteLogger</li>
  999. <li>Copy the <a href="https://github.com/camac/GregorbyteXspLibrary/blob/master/com.gregorbyte.xsp.controls/src/com/gregorbyte/xsp/component/UIMimeInspector.java">UIMimeInspector </a>class and paste it in your java elements section</li>
  1000. <li>Copy the <a href="https://github.com/camac/GregorbyteXspLibrary/blob/master/com.gregorbyte.xsp.controls/src/com/gregorbyte/xsp/config/gregorbyte-mimeinspector.xsp-config">gregorbyte-mimeinspector.xsp-config</a> file and put it in your WebContent\WEB-INF directory in your NSF</li>
  1001. <li>There is no renderer because the control renders itself, so no need for renderer or faces-conig.xml entries</li>
  1002. </ul>
  1003. <p>Here is an example of the code in the XPages Markup:</p><pre class="crayon-plain-tag">&lt;gb:mimeInspector id="mimeInspector1" value="#{document1.Body}"
  1004. showHeaders="true"
  1005. showContent="true"&gt;&lt;/gb:mimeInspector&gt;</pre><p>Here is an example of some output with &#8216;showHeaders&#8217; and &#8216;showContent&#8217;. In this example the mime being looked at is just a single text/html MimeEntity.</p>
  1006. <p><img class="aligncenter size-full wp-image-411" src="http://camerongregor.com/wp-content/uploads/2017/02/exampleMimeInspectorOutput2.jpg" alt="" width="564" height="584" srcset="http://camerongregor.com/wp-content/uploads/2017/02/exampleMimeInspectorOutput2.jpg 564w, http://camerongregor.com/wp-content/uploads/2017/02/exampleMimeInspectorOutput2-290x300.jpg 290w" sizes="(max-width: 564px) 100vw, 564px" /></p>
  1007. <p>Let me know if you have any trouble, I hope this helps somebody!</p>
  1008. ]]></content:encoded>
  1009. <wfw:commentRss>http://camerongregor.com/2017/02/14/xpages-webmail-using-mime-inspector-to-debug-mime/feed/</wfw:commentRss>
  1010. <slash:comments>3</slash:comments>
  1011. </item>
  1012. <item>
  1013. <title>Pasting Images into XPages CKEditor</title>
  1014. <link>http://camerongregor.com/2017/02/13/pasting-images-into-xpages-ckeditor/</link>
  1015. <comments>http://camerongregor.com/2017/02/13/pasting-images-into-xpages-ckeditor/#comments</comments>
  1016. <pubDate>Sun, 12 Feb 2017 22:00:21 +0000</pubDate>
  1017. <dc:creator><![CDATA[camerongregor]]></dc:creator>
  1018. <category><![CDATA[Software Development]]></category>
  1019. <category><![CDATA[ckeditor]]></category>
  1020. <category><![CDATA[xpages]]></category>
  1021.  
  1022. <guid isPermaLink="false">http://camerongregor.com/?p=392</guid>
  1023. <description><![CDATA[Programs like &#8216;Snipping Tool&#8217; on Windows, are super useful for users to make a quick snapshot, do some quick markup on the image, paste into chat/email and send. Unfortunately when using the default configuration&#46;&#46;&#46;]]></description>
  1024. <content:encoded><![CDATA[<p>Programs like &#8216;Snipping Tool&#8217; on Windows, are super useful for users to make a quick snapshot, do some quick markup on the image, paste into chat/email and send.</p>
  1025. <p>Unfortunately when using the default configuration of CKEditor in XPages (the inputRichText control), support for pasting images is not available for all browsers, and even for the ones that do support it, the images are only pasted as a PNG data URI. I have explained data URI images in <a href="http://camerongregor.com/2016/11/14/preventing-pasting-of-images-in-ckeditor/">a previous post</a>, so check that out if you are unfamiliar with it.</p>
  1026. <p>This also means the pasted images will <a href="http://stackoverflow.com/questions/40540890/configure-ckeditor-to-encode-pasted-images-as-base-64-jpg-not.png" data-rel="lightbox-0" title="">not display properly in Some versions of IBM Notes client</a>, and also if you are using the content of the inputRichText for sending email content, the pasted images will also not display in the majority of webmail / email clients. This will result in a lot of replies from recipients along the lines of &#8220;I can&#8217;t see any images&#8217;.</p>
  1027. <p>My major project recently has been a webmail interface for XPages, and accordingly I needed to solve this problem to make the users happy!</p>
  1028. <p><strong>From first solution to Current solution</strong></p>
  1029. <p>The solution to this problem, was to intercept the image paste event and instead of creating the data URI image, it uploads the image via the same mechanism that is used for the standard &#8216;image button&#8217; on the CKEditor toolbar. This involves posting the image data to a URL and interpreting the &#8216;success&#8217; response, and then inserting the corresponding &#8216;&lt;img&gt;&#8217; tag into the editor content.</p>
  1030. <p>The first solution I came up was a modification of the free <a href="http://ckeditor.com/addon/imagepaste">imagepaste CKEditor plugin</a> by Alfonso Martinez de Lizarrondo. Unfortunately this solution only worked for Firefox. So it was a good start but not completely useful because some users preferred to use Chrome.</p>
  1031. <p>Alfonso had also released a paid plugin called <a href="https://alfonsoml.blogspot.com.au/p/simpleuploads-plugin-for-ckeditor.html">SimpleUploads</a>, which included support for other browsers such as Chrome and Internet Explorer. So I purchased this plugin and ripped the parts out of it that were needed to support chrome and internet explorer, and modified my existing solution.</p>
  1032. <p>This was great for me! Now my users could paste from Firefox, Chrome and Internet Explorer. However I could not share this solution on my blog because it included source code from the plugin that I purchased. The plugin is not licensed as Open Source, so this would not be within my rights to do so.</p>
  1033. <p>So when a stackoverflow user had the same problem,  I decided to amend the solution so that I could share it whilst respecting the licensing conditions of the original plugin author.</p>
  1034. <p>Alfonso had actually already included some callback functions which allowed customisation of the simpleuploads plugin. It turns out I could actually reconfigure my solution to be an &#8216;extension&#8217; of the simpleuploads plugin.</p>
  1035. <p>I modified the solution to use the callbacks and it was almost perfect, but I just needed Alfonso to make 1 minor change to the SimpleUploads plugin. He agreed to make the change and his latest version of SimpleUploads includes the modification to allow my solution to work.</p>
  1036. <h3><strong>The Solution</strong></h3>
  1037. <h4>Obtain SimpleUploads</h4>
  1038. <p>So as mentioned above, you will need to purchase the SimpleUploads plugin and make sure you have the latest version or at least verson 4.4.4</p>
  1039. <p>It starts at 10 Euros for a single site licence.</p>
  1040. <ul>
  1041. <li>If you are a new customer you will automatically receive the latest version.</li>
  1042. <li>If you had previously bought SimpleUploads you may need to request the latest version from Alfonso. Alfonso has not pushed the new version out to his customer mailing list yet as the change is not significant for other users.</li>
  1043. </ul>
  1044. <h4>Install SimpleUploads</h4>
  1045. <p>Once you have obtained the simple uploads plugin, put it in your WEB-INF directory of the NSF</p>
  1046. <p><img class="aligncenter size-full wp-image-401" src="http://camerongregor.com/wp-content/uploads/2017/02/simpleuploadswebinf.jpg" alt="" width="657" height="332" srcset="http://camerongregor.com/wp-content/uploads/2017/02/simpleuploadswebinf.jpg 657w, http://camerongregor.com/wp-content/uploads/2017/02/simpleuploadswebinf-300x152.jpg 300w" sizes="(max-width: 657px) 100vw, 657px" /></p>
  1047. <h4>Create a new Javascript library in your nsf</h4>
  1048. <p>This library will configure CKEditor to know the whereabouts of simpleuploads plugin, and also include the necessary extensions to simpleuploads to allow it to work with XPages.</p>
  1049. <p>I have called it &#8216;xspSimpleUploads&#8217;</p>
  1050. <p><img class="size-full wp-image-402 aligncenter" src="http://camerongregor.com/wp-content/uploads/2017/02/xspsimpleuploads.jpg" alt="" width="223" height="91" /></p>
  1051. <p>Here are the contents of the library:</p><pre class="crayon-plain-tag">// Get the Base Url of the nsf so we can figure out the location of simpleuploads plugin
  1052. var urlBase = document.URL.substr(0,document.URL.lastIndexOf('.nsf') + 4);
  1053.  
  1054. // Tell CKEditor not to include a random timestamp that forces cache (because Domino gets upset if it is there)
  1055. CKEDITOR.timestamp = '';
  1056.  
  1057. // Tell CKEditor to include simpleuploads as an available plugin
  1058. CKEDITOR.plugins.addExternal('simpleuploads', urlBase + '/simpleuploads/', 'plugin.js');
  1059.  
  1060. // Disable the file upload part of SimpleUploads
  1061. CKEDITOR.config.filebrowserUploadUrl = 'dontuploadfiles';
  1062. CKEDITOR.on('instanceReady', function(e) {
  1063.  
  1064. var currPageUrl = document.location.href;
  1065.  
  1066. e.editor.config.filebrowserUploadUrl = false;
  1067.  
  1068. if (currPageUrl.indexOf('?') === -1) {
  1069. e.editor.config.filebrowserImageUploadUrl = currPageUrl;
  1070. } else {
  1071. e.editor.config.filebrowserImageUploadUrl = currPageUrl.substring(0, currPageUrl.indexOf('?'));
  1072. }
  1073.  
  1074.    // Listen for Start Upload
  1075.    e.editor.on( 'simpleuploads.startUpload' , function(ev) {
  1076.  
  1077.        var data = ev.data;
  1078.  
  1079.        var id = ev.editor.element.$.id;
  1080.        
  1081.        if (ev.data.url.indexOf('?') !== -1) {
  1082.         ev.data.url = ev.data.url.substring(0, currPageUrl.indexOf('?'));
  1083.        }        
  1084.        
  1085.        // Add the xpage viewid and the client id of the inputRichText
  1086.        ev.data.url += '?$$axtarget=' + id + '&amp;$$viewid=' + XSP.findForm(id)['$$viewid'].value;        
  1087.  
  1088.        var extraFields = ev.data.extraFields || {};
  1089.  
  1090.        var imgid = CKEDITOR.tools.getNextId();
  1091.        var fileName = imgid;
  1092.  
  1093.        ev.data.name = fileName + '.png';
  1094.  
  1095.        CKEDITOR.tools.extend(extraFields, {
  1096.            'fileName': fileName + '.png',
  1097.            'actionType': 'embedded-image'
  1098.        });
  1099.  
  1100.        ev.data.extraFields = extraFields;
  1101.  
  1102.        // Tell the Request to expect a html document in response
  1103.        ev.data.xhr = new XMLHttpRequest();
  1104.        ev.data.xhr.responseType = 'document';
  1105.    
  1106.    });
  1107.    
  1108.    e.editor.on( 'simpleuploads.serverResponse' , function(ev) {
  1109.      
  1110.        var respXml = ev.data.xhr.responseXML;
  1111.        var jsonText; // = respXml.documentElement.childNodes[1].firstChild.innerHTML;;
  1112.        
  1113.        if (!respXml) {
  1114.         /*
  1115. I am aware that parsing html with regex is not recommended
  1116. however the simple upload plugin does not allow for setting
  1117. the responseType to document, and the response should be the same
  1118.         */
  1119.         var re = /.*&lt;textarea&gt;(.*)&lt;\/textarea&gt;.*/;
  1120.         var jsonText = ev.data.xhr.responseText.replace(re, "$1");
  1121.      
  1122.        } else {
  1123.         jsonText = respXml.documentElement.childNodes[1].firstChild.innerHTML;
  1124.        }
  1125.  
  1126. var embeddedImage = XSP.fromJson(jsonText);
  1127.  
  1128. if (null != embeddedImage &amp;&amp; embeddedImage.statusMessage == "SUCCESS") {
  1129.  
  1130. var imageElement = ev.editor.document.createElement(embeddedImage.tag);
  1131. imageElement.setAttribute("src", embeddedImage.src);
  1132. imageElement.setAttribute("data-cke-saved-src", embeddedImage.dataSrc);
  1133. imageElement.setAttribute("alt", ev.data.altText);
  1134.  
  1135. ev.editor.insertElement(imageElement);
  1136. } else {
  1137. if (null != embeddedImage) {
  1138. XSP.alert(embeddedImage.statusMessage);
  1139. } else {
  1140. console.log("Error:IbmImage:ibmxspimage.js:load");
  1141. }
  1142. }
  1143. var el = ev.editor.document.getById( ev.data.data.id );
  1144. if (!el) {
  1145. } else {
  1146. if (ev.data.data.originalNode)
  1147. el.$.parentNode.replaceChild(ev.data.data.originalNode, el.$);
  1148. else
  1149. el.remove();
  1150. }
  1151. ev.editor.fire("updateSnapshot");
  1152. ev.cancel();        
  1153.        
  1154.    });    
  1155.    
  1156. });</pre><p><strong>Configure your XPage and Ckeditor</strong></p>
  1157. <p>You must include the Script Library on your xpage, and you must tell your CKeditor to use simpleuploads via the extraPlugins dojoAttribute</p>
  1158. <p>Here is an example page that I am using with the key points highlighted.</p><pre class="crayon-plain-tag">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
  1159. &lt;xp:view xmlns:xp="http://www.ibm.com/xsp/core"&gt;
  1160.  
  1161. &lt;xp:this.resources&gt;
  1162. &lt;xp:script src="/xspSimpleUploads.js" clientSide="true"&gt;&lt;/xp:script&gt;
  1163. &lt;/xp:this.resources&gt;
  1164.  
  1165. &lt;xp:this.data&gt;
  1166. &lt;xp:dominoDocument var="document1" formName="Post"&gt;&lt;/xp:dominoDocument&gt;
  1167. &lt;/xp:this.data&gt;
  1168. &lt;xp:table&gt;
  1169. &lt;xp:tr&gt;
  1170. &lt;xp:td&gt;
  1171. &lt;xp:label value="Subject:" id="subject_Label1" for="subject1"&gt;&lt;/xp:label&gt;
  1172. &lt;/xp:td&gt;
  1173. &lt;xp:td&gt;
  1174. &lt;xp:inputText value="#{document1.Subject}" id="subject1"&gt;&lt;/xp:inputText&gt;
  1175. &lt;/xp:td&gt;
  1176. &lt;/xp:tr&gt;
  1177. &lt;/xp:table&gt;
  1178. &lt;xp:inputRichText id="inputRichText1" value="#{document1.Body}"&gt;
  1179. &lt;xp:this.dojoAttributes&gt;
  1180. &lt;xp:dojoAttribute name="extraPlugins" value="simpleuploads"&gt;&lt;/xp:dojoAttribute&gt;
  1181. &lt;/xp:this.dojoAttributes&gt;
  1182. &lt;/xp:inputRichText&gt;
  1183. &lt;xp:br&gt;&lt;/xp:br&gt;
  1184. &lt;xp:button value="Save" id="button1"&gt;
  1185. &lt;xp:eventHandler event="onclick" submit="true" refreshMode="complete"&gt;
  1186. &lt;xp:this.action&gt;
  1187. &lt;xp:saveDocument var="document1"&gt;&lt;/xp:saveDocument&gt;
  1188. &lt;/xp:this.action&gt;
  1189. &lt;/xp:eventHandler&gt;
  1190. &lt;/xp:button&gt;
  1191.  
  1192. &lt;xp:viewPanel rows="30" id="viewPanel1" pageName="/Home.xsp"&gt;
  1193. &lt;xp:this.facets&gt;
  1194. &lt;xp:pager partialRefresh="true" layout="Previous Group Next" xp:key="headerPager" id="pager1"&gt;&lt;/xp:pager&gt;
  1195. &lt;/xp:this.facets&gt;
  1196. &lt;xp:this.data&gt;
  1197. &lt;xp:dominoView var="view1" viewName="Posts"&gt;&lt;/xp:dominoView&gt;
  1198. &lt;/xp:this.data&gt;
  1199. &lt;xp:viewColumn columnName="Subject" id="viewColumn1" displayAs="link" openDocAsReadonly="true"&gt;
  1200. &lt;xp:viewColumnHeader value="Subject" id="viewColumnHeader1"&gt;&lt;/xp:viewColumnHeader&gt;
  1201. &lt;/xp:viewColumn&gt;
  1202. &lt;/xp:viewPanel&gt;
  1203.  
  1204. &lt;/xp:view&gt;</pre><p></p>
  1205. <h3>Result:</h3>
  1206. <p>Your users should be able to paste images with no worries!</p>
  1207. <p>As usual, I am sure I haven&#8217;t thought of everything so if you have any problems with the above, then please let me know!</p>
  1208. <h3>Demonstration</h3>
  1209. <p>As requested by Timothy Briley, here is a demonstration video which demonstrates the problem and shows the steps to implement the solution.</p>
  1210. <div class="video-container"><iframe width="500" height="281" src="https://www.youtube.com/embed/LYQpJ3qqwsQ?feature=oembed&#038;wmode=opaque" frameborder="0" allowfullscreen></iframe></div>
  1211. ]]></content:encoded>
  1212. <wfw:commentRss>http://camerongregor.com/2017/02/13/pasting-images-into-xpages-ckeditor/feed/</wfw:commentRss>
  1213. <slash:comments>5</slash:comments>
  1214. </item>
  1215. <item>
  1216. <title>Preventing pasting of remotely hosted images in CKEditor</title>
  1217. <link>http://camerongregor.com/2016/11/15/preventing-pasting-of-remotely-hosted-images-in-ckeditor/</link>
  1218. <comments>http://camerongregor.com/2016/11/15/preventing-pasting-of-remotely-hosted-images-in-ckeditor/#comments</comments>
  1219. <pubDate>Mon, 14 Nov 2016 23:21:59 +0000</pubDate>
  1220. <dc:creator><![CDATA[camerongregor]]></dc:creator>
  1221. <category><![CDATA[Software Development]]></category>
  1222. <category><![CDATA[ckeditor]]></category>
  1223. <category><![CDATA[webmail]]></category>
  1224. <category><![CDATA[xpages]]></category>
  1225.  
  1226. <guid isPermaLink="false">http://camerongregor.com/?p=384</guid>
  1227. <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>
  1228. <content:encoded><![CDATA[<p>In the previous post, I showed <a href="http://camerongregor.com/2016/11/14/preventing-pasting-of-images-in-ckeditor/">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>
  1229. <p>This post is part of my <a href="http://camerongregor.com/2016/04/19/tips-for-creating-a-webmail-ui-with-xpages/">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>
  1230. <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 />
  1231. 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>
  1232. <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>
  1233. <p>The result of all of this is more complaints of &#8220;I can&#8217;t see any image&#8221;</p>
  1234. <p><strong>Another CKEditor Plugin!</strong></p>
  1235. <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', {
  1236.  
  1237. init : function(editor) {
  1238.  
  1239. function replaceImgText(html) {
  1240.  
  1241. var replacedSomething = false;
  1242.  
  1243. var ret = html.replace(/&lt;img[^&gt;]*src="http.*?"[^&gt;]*&gt;/gi, function(img) {
  1244. replacedSomething = true;
  1245. return '';
  1246. });
  1247.  
  1248. if (replacedSomething) {
  1249. 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.');
  1250. }
  1251.  
  1252. return ret;
  1253. }
  1254.  
  1255. function chkImg() {
  1256.  
  1257. // don't execute code if the editor is readOnly
  1258. if (editor.readOnly)
  1259. return;
  1260.  
  1261. setTimeout(function() {
  1262. editor.document.$.body.innerHTML = replaceImgText(editor.document.$.body.innerHTML);
  1263. }, 100);
  1264. }
  1265.  
  1266. editor.on('contentDom', function() {
  1267. // For Firefox
  1268. editor.document.on('drop', chkImg);
  1269. // For IE
  1270. editor.document.getBody().on('drop', chkImg);
  1271. });
  1272.  
  1273. editor.on('paste', function(e) {
  1274.  
  1275. var html = e.data.dataValue;
  1276. if (!html) {
  1277. return;
  1278. }
  1279.  
  1280. e.data.dataValue = replaceImgText(html);
  1281.  
  1282. });
  1283.  
  1284. }
  1285.  
  1286. });</pre><p>Save the above javascript as a script library in your nsf, called &#8216;blockpasteimagelink&#8217;</p>
  1287. <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;
  1288. &lt;xp:view xmlns:xp="http://www.ibm.com/xsp/core"&gt;
  1289.  
  1290. &lt;xp:this.data&gt;
  1291. &lt;xp:dominoDocument var="document1"&gt;&lt;/xp:dominoDocument&gt;
  1292. &lt;/xp:this.data&gt;
  1293.  
  1294. &lt;xp:this.resources&gt;
  1295. &lt;xp:script src="/blockpasteimagelink.js" clientSide="true"&gt;&lt;/xp:script&gt;
  1296. &lt;/xp:this.resources&gt;
  1297. &lt;xp:inputRichText id="inputRichText1" value="#{document1.body}"&gt;
  1298. &lt;xp:this.dojoAttributes&gt;
  1299. &lt;xp:dojoAttribute name="extraPlugins" value="blockpasteimagelink"&gt;&lt;/xp:dojoAttribute&gt;
  1300. &lt;/xp:this.dojoAttributes&gt;
  1301. &lt;/xp:inputRichText&gt;
  1302. &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>
  1303. <p><img class="aligncenter size-full wp-image-385" src="http://camerongregor.com/wp-content/uploads/2016/11/PreventPasteCopyJackalope.jpg" alt="preventpastecopyjackalope" width="368" height="400" srcset="http://camerongregor.com/wp-content/uploads/2016/11/PreventPasteCopyJackalope.jpg 368w, http://camerongregor.com/wp-content/uploads/2016/11/PreventPasteCopyJackalope-276x300.jpg 276w" sizes="(max-width: 368px) 100vw, 368px" /></p>
  1304. <p>Then I will paste it into my CKEditor, where I will receive a warning&#8230;</p>
  1305. <p><img class="aligncenter size-full wp-image-386" src="http://camerongregor.com/wp-content/uploads/2016/11/PreventPasteWarning.jpg" alt="preventpastewarning" width="638" height="262" srcset="http://camerongregor.com/wp-content/uploads/2016/11/PreventPasteWarning.jpg 638w, http://camerongregor.com/wp-content/uploads/2016/11/PreventPasteWarning-300x123.jpg 300w" sizes="(max-width: 638px) 100vw, 638px" /></p>
  1306. <p>And after clicking ok, I can see that everything except the image has been pasted&#8230;</p>
  1307. <p><img class="aligncenter size-full wp-image-387" src="http://camerongregor.com/wp-content/uploads/2016/11/PreventPasteAfter.jpg" alt="preventpasteafter" width="514" height="269" srcset="http://camerongregor.com/wp-content/uploads/2016/11/PreventPasteAfter.jpg 514w, http://camerongregor.com/wp-content/uploads/2016/11/PreventPasteAfter-300x157.jpg 300w" sizes="(max-width: 514px) 100vw, 514px" /></p>
  1308. <p><strong>Summary</strong></p>
  1309. <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>
  1310. <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>
  1311. <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>
  1312. ]]></content:encoded>
  1313. <wfw:commentRss>http://camerongregor.com/2016/11/15/preventing-pasting-of-remotely-hosted-images-in-ckeditor/feed/</wfw:commentRss>
  1314. <slash:comments>1</slash:comments>
  1315. </item>
  1316. <item>
  1317. <title>Preventing Pasting of Images in CKEditor</title>
  1318. <link>http://camerongregor.com/2016/11/14/preventing-pasting-of-images-in-ckeditor/</link>
  1319. <comments>http://camerongregor.com/2016/11/14/preventing-pasting-of-images-in-ckeditor/#comments</comments>
  1320. <pubDate>Mon, 14 Nov 2016 00:43:29 +0000</pubDate>
  1321. <dc:creator><![CDATA[camerongregor]]></dc:creator>
  1322. <category><![CDATA[Software Development]]></category>
  1323. <category><![CDATA[ckeditor]]></category>
  1324. <category><![CDATA[webmail]]></category>
  1325. <category><![CDATA[xpages]]></category>
  1326.  
  1327. <guid isPermaLink="false">http://camerongregor.com/?p=374</guid>
  1328. <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>
  1329. <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>
  1330. <p>After investigating, it was caused by the images being embedded using Data URIs. <a href="http://stackoverflow.com/questions/6070196/what-is-data-uri-support-like-in-major-email-client-software">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>
  1331. <p><strong>What is a Data URI?</strong></p>
  1332. <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="https://en.wikipedia.org/wiki/Data_URI_scheme#HTML">I lifted from wikipedia</a>:</p><pre class="crayon-plain-tag">&lt;img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA
  1333. AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO
  1334. 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
  1335. Content-Type: image/jpeg
  1336. Content-Disposition: inline;
  1337. filename="reddot_1479074021464.JPG"
  1338. Content-ID: &lt;reddot_1479074021464&gt;
  1339. Content-Transfer-Encoding: base64
  1340.  
  1341. /9j/4AAQSkZJRgABAQEAYABgAAD/4RD4RXhpZgAATU0AKgAAAAgABAE7AAIAAAAPAAAISodpAAQA
  1342. ...more binary data...
  1343. iigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/9k=
  1344.  
  1345.  
  1346. --==IFJRGLKFGIR32748UHRUHIHD--</pre><p><strong>Sending Embedded Images in Emails<br />
  1347. </strong></p>
  1348. <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>
  1349. <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>
  1350. <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>
  1351. <p><strong>Preventing Pasting of Images using a CKEditor plugin</strong></p>
  1352. <p>After a bit of googling I found that <a href="http://www.isummation.com/blog/block-drag-drop-image-or-direct-image-paste-into-ckeditor-using-firefox/">someone else had already come up with a plugin which prevents pasted images,</a> and it worked without modification for XPages</p>
  1353. <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', {
  1354.  
  1355. init : function(editor) {
  1356.  
  1357. function replaceImgText(html) {
  1358.  
  1359. 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) {
  1360. alert("Pasting Image Data is not allowed. Please use the Image Upload button (if available).");
  1361. return '';
  1362. });
  1363.  
  1364. return ret;
  1365. }
  1366.  
  1367. function chkImg() {
  1368. // don't execute code if the editor is readOnly
  1369. if (editor.readOnly)
  1370. return;
  1371.  
  1372. setTimeout(function() {
  1373. editor.document.$.body.innerHTML = replaceImgText(editor.document.$.body.innerHTML);
  1374. }, 100);
  1375. }
  1376.  
  1377. editor.on('contentDom', function() {
  1378. // For Firefox
  1379. editor.document.on('drop', chkImg);
  1380. // For IE
  1381. editor.document.getBody().on('drop', chkImg);
  1382. });
  1383.  
  1384. editor.on('paste', function(e) {
  1385.  
  1386. var html = e.data.dataValue;
  1387. if (!html) {
  1388. return;
  1389. }
  1390.  
  1391. e.data.dataValue = replaceImgText(html);
  1392. });
  1393.  
  1394. } // Init
  1395. });</pre><p>To use the plugin, copy the above code into a Client Side JavaScript Library &#8216;blockimagepaste&#8217;.</p>
  1396. <p><img class="aligncenter size-full wp-image-378" src="http://camerongregor.com/wp-content/uploads/2016/11/preventpasteScriptLib-1.jpg" alt="preventpastescriptlib" width="699" height="309" srcset="http://camerongregor.com/wp-content/uploads/2016/11/preventpasteScriptLib-1.jpg 699w, http://camerongregor.com/wp-content/uploads/2016/11/preventpasteScriptLib-1-300x133.jpg 300w" sizes="(max-width: 699px) 100vw, 699px" /></p>
  1397. <p>&nbsp;</p>
  1398. <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;
  1399. &lt;xp:view xmlns:xp="http://www.ibm.com/xsp/core"&gt;
  1400.  
  1401. &lt;xp:this.data&gt;
  1402. &lt;xp:dominoDocument var="document1"&gt;&lt;/xp:dominoDocument&gt;
  1403. &lt;/xp:this.data&gt;
  1404.  
  1405. &lt;xp:this.resources&gt;
  1406. &lt;xp:script src="/blockimagepaste.js" clientSide="true"&gt;&lt;/xp:script&gt;
  1407. &lt;/xp:this.resources&gt;
  1408. &lt;xp:inputRichText id="inputRichText1" value="#{document1.body}"&gt;
  1409. &lt;xp:this.dojoAttributes&gt;
  1410. &lt;xp:dojoAttribute name="extraPlugins" value="blockimagepaste"&gt;&lt;/xp:dojoAttribute&gt;
  1411. &lt;/xp:this.dojoAttributes&gt;
  1412. &lt;/xp:inputRichText&gt;
  1413. &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>
  1414. <p><img class="aligncenter size-full wp-image-376" src="http://camerongregor.com/wp-content/uploads/2016/11/preventpastealert.jpg" alt="preventpastealert" width="734" height="309" srcset="http://camerongregor.com/wp-content/uploads/2016/11/preventpastealert.jpg 734w, http://camerongregor.com/wp-content/uploads/2016/11/preventpastealert-300x126.jpg 300w" sizes="(max-width: 734px) 100vw, 734px" /></p>
  1415. <p><strong>Summary and Next Steps</strong></p>
  1416. <p>So in this post we added a CKEditor plugin to our application, and configured our InputRichText to use it.</p>
  1417. <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>
  1418. <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>
  1419. <p>&nbsp;</p>
  1420. ]]></content:encoded>
  1421. <wfw:commentRss>http://camerongregor.com/2016/11/14/preventing-pasting-of-images-in-ckeditor/feed/</wfw:commentRss>
  1422. <slash:comments>3</slash:comments>
  1423. </item>
  1424. <item>
  1425. <title>Controlling the order of Script Resources (e.g. Jquery) with a Custom ViewRootRenderer</title>
  1426. <link>http://camerongregor.com/2016/09/19/controlling-the-order-of-script-resources-e-g-jquery-with-a-custom-viewrootrenderer/</link>
  1427. <comments>http://camerongregor.com/2016/09/19/controlling-the-order-of-script-resources-e-g-jquery-with-a-custom-viewrootrenderer/#comments</comments>
  1428. <pubDate>Mon, 19 Sep 2016 13:25:19 +0000</pubDate>
  1429. <dc:creator><![CDATA[camerongregor]]></dc:creator>
  1430. <category><![CDATA[Software Development]]></category>
  1431. <category><![CDATA[renderer]]></category>
  1432. <category><![CDATA[xpages]]></category>
  1433.  
  1434. <guid isPermaLink="false">http://camerongregor.com/?p=355</guid>
  1435. <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>
  1436. <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>
  1437. <p>For example jQuery and some of it&#8217;s plugins can have some issues if Dojo is encoded first.</p>
  1438. <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="http://hasselba.ch/blog/?p=1216">here</a> and <a href="http://hasselba.ch/blog/?p=1181">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>
  1439. <blockquote><p>UPDATE!! It turns out that Sven <a href="http://hasselba.ch/blog/?p=2070">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>
  1440. <p>There are only 2 downsides to this method and they are not too major.</p>
  1441. <ol>
  1442. <li>You must have resource aggregation turned on</li>
  1443. <li>You can&#8217;t use a theme to specify the resources</li>
  1444. </ol>
  1445. <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>
  1446. <p><strong>Developing the solution</strong></p>
  1447. <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>
  1448. <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>
  1449. <p>At the very Root of your XPages component tree is the UIViewRoot (corresponding to the &lt;xp:view&gt; tag.</p>
  1450. <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>
  1451. <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>
  1452. <p><strong>Creating our ViewRootRenderer</strong></p>
  1453. <p>The default View Renderer for XPages is of the class <em>com.ibm.xsp.renderkit.html_basic.ViewRootRendererEx2 </em>So we are going to extend that one and see what we can do with it.</p>
  1454. <p>I create new Java class in the NSF</p><pre class="crayon-plain-tag">package com.gregorbyte.xsp.ViewRootRenderer;
  1455.  
  1456. import com.ibm.xsp.renderkit.html_basic.ViewRootRendererEx2;
  1457.  
  1458. public class ViewRootRenderer extends ViewRootRendererEx2 {
  1459.  
  1460. }</pre><p>Now which method to override? Lets have at the ones we can and see which might be it.</p>
  1461. <p>We can use the Source =&gt; Override/Implement Methods dialog to show us some candidates.</p>
  1462. <p><img class="aligncenter size-full wp-image-357" src="http://camerongregor.com/wp-content/uploads/2016/09/viewrootoverridemethod.jpg" alt="viewrootoverridemethod" width="512" height="718" srcset="http://camerongregor.com/wp-content/uploads/2016/09/viewrootoverridemethod.jpg 512w, http://camerongregor.com/wp-content/uploads/2016/09/viewrootoverridemethod-214x300.jpg 214w" sizes="(max-width: 512px) 100vw, 512px" /></p>
  1463. <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;
  1464.  
  1465. import java.io.IOException;
  1466. import java.util.List;
  1467.  
  1468. import javax.faces.context.FacesContext;
  1469. import javax.faces.context.ResponseWriter;
  1470.  
  1471. import com.ibm.xsp.component.UIViewRootEx;
  1472. import com.ibm.xsp.renderkit.html_basic.ViewRootRendererEx2;
  1473. import com.ibm.xsp.resource.Resource;
  1474.  
  1475. public class ViewRootRenderer extends ViewRootRendererEx2 {
  1476.  
  1477. @Override
  1478. protected void encodeResourcesList(FacesContext context, UIViewRootEx viewRoot,
  1479. ResponseWriter writer, List&lt;Resource&gt; resources) throws IOException {
  1480.  
  1481. // Write a comment so we can see in the generated HTML we are in the right spot
  1482. writer.writeComment("Before Resources");
  1483. writer.write("\n"); // new line
  1484. super.encodeResourcesList(context, viewRoot, writer, resources);
  1485.  
  1486. // Write a comment so we can see in the generated HTML we are in the right spot
  1487. writer.writeComment("After Resources");
  1488. writer.write("\n"); // new line
  1489. }
  1490.  
  1491. }</pre><p>Next thing to do is register our Renderer, and then tell XPages to Use it for our view root.</p>
  1492. <p><strong>Register the Renderer</strong></p>
  1493. <p>To do this we need to tell XPages (via faces config)</p>
  1494. <ul>
  1495. <li>The Component family that our renderer is available to</li>
  1496. <li>What name do we refer to our renderer (renderer-type)</li>
  1497. <li>What is the Java Class of this renderer</li>
  1498. </ul>
  1499. <p>The component family can be found with a <a href="http://camerongregor.com/2016/03/08/ssjs-avoidance-inspect-component-properties-using-component-binding/">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>
  1500. <p>our faces config entry is like so:</p><pre class="crayon-plain-tag">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
  1501. &lt;faces-config&gt;
  1502.  &lt;!--AUTOGEN-START-BUILDER: Automatically generated by IBM Domino Designer. Do not modify.--&gt;
  1503.  &lt;!--AUTOGEN-END-BUILDER: End of automatically generated section--&gt;
  1504.  
  1505.   &lt;render-kit&gt;
  1506.   &lt;renderer&gt;
  1507.   &lt;component-family&gt;javax.faces.ViewRoot&lt;/component-family&gt;
  1508.   &lt;renderer-type&gt;com.gregorbyte.xsp.ViewRoot&lt;/renderer-type&gt;
  1509.   &lt;renderer-class&gt;com.gregorbyte.xsp.ViewRootRenderer.ViewRootRenderer&lt;/renderer-class&gt;
  1510.   &lt;/renderer&gt;
  1511.   &lt;/render-kit&gt;
  1512.  
  1513. &lt;/faces-config&gt;</pre><p><strong>Tell Xpages to use our renderer</strong></p>
  1514. <p>Here we have 3 options!</p>
  1515. <ol>
  1516. <li>Specify on an XPage by XPage basis, some XPages use ours some don&#8217;t</li>
  1517. <li>Use a theme to set the ViewRootRenderer for every page in the application</li>
  1518. <li>Override the default renderer directly in faces-config</li>
  1519. </ol>
  1520. <p><strong>Option 1: Specify on a Page by Page basis</strong></p>
  1521. <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;
  1522. &lt;xp:view xmlns:xp="http://www.ibm.com/xsp/core" rendererType="com.gregorbyte.xsp.ViewRoot"&gt;
  1523.  
  1524.  
  1525. &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 />
  1526. 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>
  1527. <p><strong>Option 2: Use a Theme to set the ViewRootRenderer for every XPage</strong></p>
  1528. <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>
  1529. <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="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="platform:/plugin/com.ibm.designer.domino.stylekits/schema/stylekit.xsd"&gt;
  1530. &lt;control&gt;
  1531. &lt;name&gt;ViewRoot&lt;/name&gt;
  1532. &lt;property&gt;
  1533. &lt;name&gt;rendererType&lt;/name&gt;
  1534. &lt;value&gt;com.gregorbyte.xsp.ViewRoot&lt;/value&gt;
  1535. &lt;/property&gt;
  1536. &lt;/control&gt;
  1537. &lt;/theme&gt;</pre><p><strong>Option 3: Override the default renderer</strong></p>
  1538. <p>This is another way, where you &#8216;hijack&#8217; the rendererType that already exists (com.ibm.xsp.ViewRootEx). This way whenever XPages tries to find the IBM renderer, it will find ours instead.</p>
  1539. <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;
  1540. &lt;faces-config&gt;
  1541.  &lt;render-kit&gt;
  1542.    &lt;renderer&gt;
  1543.      &lt;component-family&gt;javax.faces.ViewRoot&lt;/component-family&gt;
  1544. &lt;!--      &lt;renderer-type&gt;com.gregorbyte.xsp.ViewRoot&lt;/renderer-type&gt;--&gt;
  1545.      &lt;renderer-type&gt;com.ibm.xsp.ViewRootEx&lt;/renderer-type&gt;
  1546.      &lt;renderer-class&gt;com.gregorbyte.xsp.ViewRootRenderer.ViewRootRenderer&lt;/renderer-class&gt;
  1547.    &lt;/renderer&gt;
  1548.  &lt;/render-kit&gt;
  1549.  &lt;!--AUTOGEN-START-BUILDER: Automatically generated by IBM Domino Designer. Do not modify.--&gt;
  1550.  &lt;!--AUTOGEN-END-BUILDER: End of automatically generated section--&gt;
  1551. &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>
  1552. <p><strong>Check that our renderer is being used</strong></p>
  1553. <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>
  1554. <p><img class="aligncenter size-full wp-image-358" src="http://camerongregor.com/wp-content/uploads/2016/09/viewrootcommentsarethere.jpg" alt="viewrootcommentsarethere" width="702" height="366" srcset="http://camerongregor.com/wp-content/uploads/2016/09/viewrootcommentsarethere.jpg 702w, http://camerongregor.com/wp-content/uploads/2016/09/viewrootcommentsarethere-300x156.jpg 300w" sizes="(max-width: 702px) 100vw, 702px" /></p>
  1555. <p>Great! So this means we are able to control what is written out at these spots.</p>
  1556. <p><strong>What to do next?</strong></p>
  1557. <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
  1558. protected void encodeResourcesList(FacesContext context, UIViewRootEx viewRoot,
  1559. ResponseWriter writer, List&lt;Resource&gt; resources) throws IOException {
  1560.  
  1561. // Write a comment so we can see in the generated HTML we are in the right spot
  1562. writer.writeComment("Before Resources");
  1563. writer.write("\n"); // new line
  1564. writer.startElement("script", null);
  1565. writer.writeAttribute("type", "text/javascript", null);
  1566. writer.writeAttribute("src", "js/itsfiveoclock.js", null);
  1567. writer.endElement("script");
  1568.                writer.write("\n");
  1569. // Write a comment so we can see in the generated HTML we are in the right spot
  1570. writer.writeComment("Start Normal Resources");
  1571. writer.write("\n"); // new line
  1572. super.encodeResourcesList(context, viewRoot, writer, resources);
  1573.  
  1574. // Write a comment so we can see in the generated HTML we are in the right spot
  1575. writer.writeComment("After Resources");
  1576. writer.write("\n"); // new line
  1577. }</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>
  1578. <p><img class="aligncenter size-full wp-image-359" src="http://camerongregor.com/wp-content/uploads/2016/09/viewrootquickndirty.jpg" alt="viewrootquickndirty" width="507" height="235" srcset="http://camerongregor.com/wp-content/uploads/2016/09/viewrootquickndirty.jpg 507w, http://camerongregor.com/wp-content/uploads/2016/09/viewrootquickndirty-300x139.jpg 300w" sizes="(max-width: 507px) 100vw, 507px" /></p>
  1579. <p>So we need some sort of test to identify:</p>
  1580. <ul>
  1581. <li>which resources should be at the top</li>
  1582. <li>which should be at the bottom</li>
  1583. <li>which should be left to normal (and free to participate in resource aggregation if they like!)</li>
  1584. </ul>
  1585. <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>
  1586. <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 />
  1587. 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;
  1588. &lt;xp:view xmlns:xp="http://www.ibm.com/xsp/core"&gt;
  1589.  
  1590. &lt;xp:this.resources&gt;
  1591. &lt;xp:script src="js/loadedfrompagebefore.js" clientSide="true"&gt;
  1592. &lt;xp:this.attrs&gt;
  1593. &lt;xp:attr name="encode-position" value="before"&gt;&lt;/xp:attr&gt;
  1594. &lt;/xp:this.attrs&gt;
  1595. &lt;/xp:script&gt;
  1596. &lt;xp:script src="js/loadedfrompageafter.js" clientSide="true"&gt;
  1597. &lt;xp:this.attrs&gt;
  1598. &lt;xp:attr name="encode-position" value="after"&gt;&lt;/xp:attr&gt;
  1599. &lt;/xp:this.attrs&gt;
  1600. &lt;/xp:script&gt;
  1601.  
  1602. &lt;/xp:this.resources&gt;
  1603.  
  1604. &lt;/xp:view&gt;</pre><p>Now in the Renderer we add a bit more logic.</p>
  1605. <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">/*
  1606. * This method will inspect a Resource and return whether it should be before
  1607. * after or normal (null)
  1608. */
  1609. private String getPosition(Resource resource) {
  1610.  
  1611. // Your logic for choosing position goes here!
  1612.  
  1613. // This test is to see if the resource has a child 'attr' tag which
  1614. // specifies the encode-position of before or aftre
  1615. if (resource instanceof FacesAttrsObject) {
  1616.  
  1617. FacesAttrsObject o = (FacesAttrsObject) resource;
  1618.  
  1619. if (o.getAttrs() != null) {
  1620.  
  1621. for (Attr attr : o.getAttrs()) {
  1622.  
  1623. if (StringUtil.equals(attr.getName(), "encode-position")) {
  1624. return attr.getValue();
  1625. }
  1626. }
  1627.  
  1628. }
  1629.  
  1630. }
  1631.  
  1632. // None of our tests matched so we just return null
  1633. return null;
  1634.  
  1635. }</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
  1636. protected void encodeResourcesList(FacesContext context,
  1637. UIViewRootEx viewRoot, ResponseWriter writer,
  1638. List&lt;Resource&gt; resources) throws IOException {
  1639.  
  1640. // Create 3 Lists of Resources
  1641. List&lt;Resource&gt; before = new ArrayList&lt;Resource&gt;();
  1642. List&lt;Resource&gt; normal = new ArrayList&lt;Resource&gt;();
  1643. List&lt;Resource&gt; after = new ArrayList&lt;Resource&gt;();
  1644.  
  1645. // Process the resources into our new lists
  1646. for (Resource resource : resources) {
  1647.  
  1648. String position = getPosition(resource);
  1649.  
  1650. if (StringUtil.equals(position, "before")) {
  1651. before.add(resource);
  1652. } else if (StringUtil.equals(position, "after")) {
  1653. after.add(resource);
  1654. } else {
  1655. normal.add(resource);
  1656. }
  1657.  
  1658. }
  1659.  
  1660. // Write a comment so we can see in the generated HTML we are in the
  1661. // right spot
  1662. writer.writeComment("Before Resources");
  1663. writer.write("\n"); // new line
  1664.  
  1665. // Encode Resources manually, note we don't pass them to the super
  1666. // method as we don't want them to be aggregated
  1667. for (Resource resource : before) {
  1668. encodeResource(context, viewRoot, writer, resource);
  1669. }
  1670.  
  1671. // Write a comment so we can see in the generated HTML we are in the
  1672. // right spot
  1673. writer.writeComment("Start Normal Resources");
  1674. writer.write("\n"); // new line
  1675. super.encodeResourcesList(context, viewRoot, writer, normal);
  1676.  
  1677. // Write a comment so we can see in the generated HTML we are in the
  1678. // right spot
  1679. writer.writeComment("After Resources");
  1680. writer.write("\n"); // new line
  1681.  
  1682. // Encode Resources manually, note we don't pass them to the super
  1683. // method as we don't want them to be aggregated
  1684. for (Resource resource : before) {
  1685. encodeResource(context, viewRoot, writer, resource);
  1686. }
  1687.  
  1688. }</pre><p>Ok, lets check out html and see if it worked&#8230;</p>
  1689. <p><img class="aligncenter size-full wp-image-361" src="http://camerongregor.com/wp-content/uploads/2016/09/viewrootpagescripts.jpg" alt="viewrootpagescripts" width="662" height="272" srcset="http://camerongregor.com/wp-content/uploads/2016/09/viewrootpagescripts.jpg 662w, http://camerongregor.com/wp-content/uploads/2016/09/viewrootpagescripts-300x123.jpg 300w" sizes="(max-width: 662px) 100vw, 662px" /></p>
  1690. <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>
  1691. <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>
  1692. <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">/*
  1693. * This method will inspect a Resource and return whether it should be on
  1694. * top or bottom or normal (null)
  1695. */
  1696. private String getPosition(Resource resource) {
  1697.  
  1698. // Your logic for choosing position goes here!
  1699.  
  1700. // This test is to see if the resource has a child 'attr' tag which
  1701. // specifies the encode-position of before or aftre
  1702. if (resource instanceof FacesAttrsObject) {
  1703.  
  1704. FacesAttrsObject o = (FacesAttrsObject) resource;
  1705.  
  1706. if (o.getAttrs() != null) {
  1707.  
  1708. for (Attr attr : o.getAttrs()) {
  1709.  
  1710. if (StringUtil.equals(attr.getName(), "encode-position")) {
  1711. return attr.getValue();
  1712. }
  1713. }
  1714.  
  1715. }
  1716.  
  1717. }
  1718. if (resource instanceof ScriptResource) {
  1719.  
  1720. // Cast it to be a ScriptResource so we can access it as such
  1721. ScriptResource sr = (ScriptResource) resource;
  1722.  
  1723. String type = sr.getType();
  1724.  
  1725. if (StringUtil.isNotEmpty(type)) {
  1726.  
  1727. if (type.endsWith("/before")) {
  1728.  
  1729. // Fix Up the Type
  1730. sr.setType(type.replace("/before", ""));
  1731.  
  1732. // Add an attribute instead so that next time it is check it
  1733. // will use that method
  1734. sr.addAttr(new Attr("encode-position", "before"));
  1735.  
  1736. return "before";
  1737. } else if (type.endsWith("/after")) {
  1738.  
  1739. // Fix Up the Type
  1740. sr.setType(type.replace("/after", ""));
  1741.  
  1742. // Add an attribute instead so that next time it is check it
  1743. // will use that method
  1744. sr.addAttr(new Attr("encode-position", "after"));
  1745.  
  1746. return "after";
  1747.  
  1748. }
  1749.  
  1750. }
  1751.  
  1752. } else if (resource instanceof StyleSheetResource) {
  1753.  
  1754. StyleSheetResource ssr = (StyleSheetResource) resource;
  1755. // You could do the same type of thing here for stylesheet
  1756.  
  1757. } else if (resource instanceof LinkResource) {
  1758.  
  1759. LinkResource lr = (LinkResource) resource;
  1760. // You could do the same type of thing here for Link Resource
  1761. //maybe even use something like title or Styleclass instead
  1762. //lr.getStyleClass();
  1763. //lr.getTitle();
  1764.  
  1765. }
  1766.  
  1767. // None of our tests matched so we just return null
  1768. return null;
  1769.  
  1770. }</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="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="platform:/plugin/com.ibm.designer.domino.stylekits/schema/stylekit.xsd"&gt;
  1771. &lt;resources&gt;
  1772. &lt;script src="js/loadfromthemebefore.js" clientSide="true" type="text/javascript/before"/&gt;
  1773. &lt;script src="js/loadfromthemeafter.js" clientSide="true" type="text/javascript/after"/&gt;
  1774. &lt;/resources&gt;
  1775. &lt;control&gt;
  1776. &lt;name&gt;ViewRoot&lt;/name&gt;
  1777. &lt;property&gt;
  1778. &lt;name&gt;rendererType&lt;/name&gt;
  1779. &lt;value&gt;com.gregorbyte.xsp.ViewRoot&lt;/value&gt;
  1780. &lt;/property&gt;
  1781. &lt;/control&gt;
  1782. &lt;/theme&gt;</pre><p>and check our html to see if it worked!</p>
  1783. <p><img class="aligncenter size-full wp-image-362" src="http://camerongregor.com/wp-content/uploads/2016/09/viewrootwiththemes.jpg" alt="viewrootwiththemes" width="678" height="375" srcset="http://camerongregor.com/wp-content/uploads/2016/09/viewrootwiththemes.jpg 678w, http://camerongregor.com/wp-content/uploads/2016/09/viewrootwiththemes-300x166.jpg 300w" sizes="(max-width: 678px) 100vw, 678px" /></p>
  1784. <p><strong>Conclusion</strong></p>
  1785. <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>
  1786. <p>I have put these example files in a <a href="https://github.com/camac/ViewRootRenderer">github repository</a>, let me know if any questions / problems!</p>
  1787. <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>
  1788. ]]></content:encoded>
  1789. <wfw:commentRss>http://camerongregor.com/2016/09/19/controlling-the-order-of-script-resources-e-g-jquery-with-a-custom-viewrootrenderer/feed/</wfw:commentRss>
  1790. <slash:comments>2</slash:comments>
  1791. </item>
  1792. </channel>
  1793. </rss>
  1794.  

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:

http://www.feedvalidator.org/check.cgi?url=http%3A//camerongregor.com/category/software-development/feed/

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