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="https://camerongregor.com/category/software-development/feed/" rel="self" type="application/rss+xml" />
  13. <link>https://camerongregor.com</link>
  14. <description>Sharing Tips, Tools and Techniques for XPages Developers</description>
  15. <lastBuildDate>Tue, 18 Jun 2019 23:34:28 +0000</lastBuildDate>
  16. <language>en-AU</language>
  17. <sy:updatePeriod>
  18. hourly </sy:updatePeriod>
  19. <sy:updateFrequency>
  20. 1 </sy:updateFrequency>
  21. <generator>https://wordpress.org/?v=6.5.2</generator>
  22. <item>
  23. <title>Our current Deployment setup: Github + Jenkins + BuildXPages</title>
  24. <link>https://camerongregor.com/2019/06/19/our-current-deployment-setup-github-jenkins-buildxpages/</link>
  25. <comments>https://camerongregor.com/2019/06/19/our-current-deployment-setup-github-jenkins-buildxpages/#comments</comments>
  26. <dc:creator><![CDATA[camerongregor]]></dc:creator>
  27. <pubDate>Tue, 18 Jun 2019 14:17:32 +0000</pubDate>
  28. <category><![CDATA[Software Development]]></category>
  29. <category><![CDATA[buildxpages]]></category>
  30. <category><![CDATA[jenkins]]></category>
  31. <guid isPermaLink="false">http://camerongregor.com/?p=589</guid>
  32.  
  33. <description><![CDATA[I recently received a question from Patrick Kwinten about whether I am still using my BuildXPages project, and whether I have involved Jenkins in the setup. The answer is yes, I still use BuildXPages&#46;&#46;&#46;]]></description>
  34. <content:encoded><![CDATA[
  35. <p>I recently received a question from Patrick Kwinten about whether I am still using my <a href="http://camac.github.io/BuildXPages/">BuildXPages </a>project, and whether I have involved Jenkins in the setup.</p>
  36.  
  37.  
  38.  
  39. <p>The answer is yes, I still use BuildXPages on a daily basis, and in regards to Jenkins, I have been using Jenkins for almost 5 years to build and deploy our XPages projects. I decided to keep the BuildXPages Documentation &#8216;agnostic&#8217; on the choice of Build server, so as to focus just on the library and the tasks it can achieve. The point was that you should be able to use any build server you like. So this is the reason Jenkins was not really mentioned in the documentation.</p>
  40.  
  41.  
  42.  
  43. <p>It has been more than a year since I have done a blog post so I thought instead of responding to Patrick directly, it would be a good excuse to finally put another blog post out there! Maybe it will spark me into action a bit more and I might make some more posts after, well see. I have to be honest, the constant &#8216;XPages is dead&#8217; mantra from here there and everywhere, combined with a recently developed addiction to online chess has a negative effect on my contributions to the community!</p>
  44.  
  45.  
  46.  
  47. <h2 class="wp-block-heading">What is our Current Setup?</h2>
  48.  
  49.  
  50.  
  51. <p>This will just be a rough overview of our current setup. You can see a more detailed post on our setup from about 5 years ago <a href="https://camerongregor.com/2014/08/09/build-system-for-xpages-and-osgi-plugins/">here</a>. Many of the details are the same but I will just go over it quickly here in a &#8220;Stream of consciousness&#8221; so I can get this blog post out quickly. </p>
  52.  
  53.  
  54.  
  55. <p>All our XPages related work is done in one monolithic repository, hosted as a private repository on Github. Initially we had multiple repositories but it was just way too much overhead to be switching branches / merging on multiple repositories. Moving to a single repository simplified everything greatly and I&#8217;m very glad we did.</p>
  56.  
  57.  
  58.  
  59. <p>Our repository consists of about 15+ different NSFs, and maybe 20+ custom java plugins. </p>
  60.  
  61.  
  62.  
  63. <p>All the NSFs are edited in Domino Designer and synchronised to the ODP using the Team Development NSF to ODP sync and of course they are using Swiper! </p>
  64.  
  65.  
  66.  
  67. <p>All our Java plugins are developed using Eclipse IDE. Our java plugins consist of a custom java framework (which has no dependency on XPages so it can ran as background tasks etc.) which provides the concept of a custom &#8216;application&#8217; with &#8216;services&#8217;, &#8216;settings&#8217;, POJO Model configurations. The framework manages mapping the Models to the NotesDocuments and back etc. among a lot of other things.<br>Our plugins then also contain a custom XPages library which provides an additional layer of our framework that includes the XPages dependency. It has all the XPages Controls that we have developed, it has other extensions that we have made on top of the extension library (things like the <a href="https://camerongregor.com/2017/12/05/jdbcnotesdocuments/">Notes based JDBC provider</a>). It also contains our custom renderers for the SmartAdmin wrapbootstrap theme (v3), which is our base theme for our applications.</p>
  68.  
  69.  
  70.  
  71. <p>In addition to these base framework plugins, we have specific plugins for each custom &#8216;application&#8217;. These provide the custom java code which defines the actual business applications that we deliver.</p>
  72.  
  73.  
  74.  
  75. <h2 class="wp-block-heading">Branching strategy</h2>
  76.  
  77.  
  78.  
  79. <p>We initially went with git-flow because it had a pretty diagram that explained it, and it was built directly into Sourcetree which is our git client of choice. However it quickly became apparent that the git-flow branching model does not suit the way we deliver our product. </p>
  80.  
  81.  
  82.  
  83. <p>Instead we moved to the <a href="https://guides.github.com/introduction/flow/">github-flow</a> method, in which there is one &#8216;master&#8217; branch, and everything else is just hanging off that. Read the link for a better explanation, but again this was a much simpler strategy that suits a &#8216;continuous deployment&#8217; style of code release, as opposed to the git-flow which I believe better supports the situation that you might have to support multiple versions of your product, and need to retain the ability to apply hotfixes and patches to different versions.</p>
  84.  
  85.  
  86.  
  87. <h2 class="wp-block-heading">Branch ready for Production!</h2>
  88.  
  89.  
  90.  
  91. <p>So I have been working away on my branch and I am ready to deploy! I create a pull request on github, we have the chance to review it and decide if it is ready, if it is all ready to go then, I merge the pull request on github and now my code has made it into the master branch, ready to deploy.</p>
  92.  
  93.  
  94.  
  95. <figure class="wp-block-image"><img fetchpriority="high" decoding="async" width="787" height="791" src="https://camerongregor.com/wp-content/uploads/2019/06/image.png" alt="" class="wp-image-590" srcset="https://camerongregor.com/wp-content/uploads/2019/06/image.png 787w, https://camerongregor.com/wp-content/uploads/2019/06/image-150x150.png 150w, https://camerongregor.com/wp-content/uploads/2019/06/image-298x300.png 298w, https://camerongregor.com/wp-content/uploads/2019/06/image-768x772.png 768w, https://camerongregor.com/wp-content/uploads/2019/06/image-160x160.png 160w" sizes="(max-width: 787px) 100vw, 787px" /></figure>
  96.  
  97.  
  98.  
  99. <h2 class="wp-block-heading">Build It!</h2>
  100.  
  101.  
  102.  
  103. <p>As mentioned earlier, we use Jenkins to build and deploy our xpages applications and plugins. We have windows machine set up with Jenkins running, and also the &#8216;headless&#8217; domino designer running, and also an eclipse installation too.</p>
  104.  
  105.  
  106.  
  107. <p>Our Jenkins server used to be publicly accessible, and therefore used to receive a &#8216;ping&#8217; from github to automatically trigger a build whenever master branch was updated. But since our Jenkins machine is now only accessible from within our private network, it no longer receives the github notification. You could also set up the jenkins server to &#8216;poll&#8217; github for changes, however I have found that it is not a problem to just manually trigger the production build because I usually just do it immediately after merging the pull-request anyway.</p>
  108.  
  109.  
  110.  
  111. <p>Here is a screenshot of our Jenkins homepage, there are a bunch of jobs on there but truth is we only really use 3 of them. 1 for building and 2 for deploying.</p>
  112.  
  113.  
  114.  
  115. <p>The build job is called &#8216;Horizon&#8217; as this is our name for our system, I have set this one up as a multi-branch pipeline job, because I thought I might have a &#8216;production&#8217; and a &#8216;staging&#8217; building, but in truth we only ever do a production build so the multi-branch thing was unnecessary.</p>
  116.  
  117.  
  118.  
  119. <figure class="wp-block-image"><img decoding="async" width="1024" height="601" src="https://camerongregor.com/wp-content/uploads/2019/06/image-1-1024x601.png" alt="" class="wp-image-591" srcset="https://camerongregor.com/wp-content/uploads/2019/06/image-1-1024x601.png 1024w, https://camerongregor.com/wp-content/uploads/2019/06/image-1-300x176.png 300w, https://camerongregor.com/wp-content/uploads/2019/06/image-1-768x451.png 768w, https://camerongregor.com/wp-content/uploads/2019/06/image-1.png 1264w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>
  120.  
  121.  
  122.  
  123. <p>Looking at the page of the production branch build, you can see the jenkins &#8216;pipeline&#8217; is split into checkout, build plugins, build nsfs.</p>
  124.  
  125.  
  126.  
  127. <p>Here you can see something that happens occasionally where it fails when I introduce some new properties to some XPages controls (which I only do maybe once a month). What happens is, the plugins build successfully with the new control properties, however the Headless Domino Designer NSF Build triggers immediately afterwards, and does not have these new plugins installed, so the NSFs fail. What you can&#8217;t see is that after build #387, I manually re-install the newly built XPages library to the Headless Domino designer, and then trigger another build. It usually returns to a successful build immediately afterwards but here I got a random error for build #388 for some reason, and running the build again #389 was successful. This is a little annoying but usually most deployments don&#8217;t involve new control properties so this is a rare occurrence.</p>
  128.  
  129.  
  130.  
  131. <figure class="wp-block-image"><img decoding="async" width="1024" height="674" src="https://camerongregor.com/wp-content/uploads/2019/06/image-2-1024x674.png" alt="" class="wp-image-592" srcset="https://camerongregor.com/wp-content/uploads/2019/06/image-2-1024x674.png 1024w, https://camerongregor.com/wp-content/uploads/2019/06/image-2-300x197.png 300w, https://camerongregor.com/wp-content/uploads/2019/06/image-2-768x505.png 768w, https://camerongregor.com/wp-content/uploads/2019/06/image-2.png 1251w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>
  132.  
  133.  
  134.  
  135. <h2 class="wp-block-heading">Jenkins Pipeline file</h2>
  136.  
  137.  
  138.  
  139. <p>The Jenkins &#8216;pipeline&#8217; build is a newer way of defining the build. Here is the pipeline file that we are using. You can see it simply triggers 2 ant tasks, and reports the success / failure via email</p>
  140.  
  141.  
  142.  
  143. <pre class="urvanov-syntax-highlighter-plain-tag">pipeline{
  144.  
  145.  agent any  
  146.  
  147.  stages {
  148.  
  149.   stage('Build Plugins') {
  150.      steps {
  151.  
  152.     echo &quot;Build Plugins on ${env.BRANCH_NAME}&quot;
  153.  
  154.   withEnv( [&quot;ANT_HOME=${tool 'Default'}&quot;] ) {
  155.     bat '%ANT_HOME%/bin/ant.bat distplugins'
  156. }
  157.  
  158.    
  159.      }
  160.   }
  161.  
  162. stage('Build NSFs') {
  163.      steps {
  164.   withEnv( [&quot;ANT_HOME=${tool 'DefaultCam'}&quot;] ) {
  165.     bat '%ANT_HOME%/bin/ant.bat buildNsfs'
  166.  }
  167.  
  168.      }
  169.   }
  170.  
  171.  stage('Results') {
  172.      steps {
  173.    archive '**/com.jord.*.jar'
  174.      }
  175.   }
  176.  }
  177.  
  178.  post {
  179.    success {
  180.      emailext body: &quot;HAPI Success ${currentBuild.currentResult}&quot;, subject: &quot;BuildXPages ${currentBuild.currentResult}&quot;, to: 'xxxxxx@xxxxx'      
  181.    }
  182.    changed {
  183.   emailext body: &quot;HAPI ${currentBuild.currentResult}&quot;, subject: &quot;BuildXPages ${currentBuild.currentResult}&quot;, to: 'xxxxx@xxxxx'      
  184.    }
  185.    failure {
  186.   emailext body: 'HAPI Failed', subject: &quot;BuildXPages Failed&quot;, to: 'xxxxxx@xxxxxxx'      
  187.    }
  188.  }
  189.  
  190. }</pre>
  191.  
  192.  
  193.  
  194. <h2 class="wp-block-heading">Ant Tasks</h2>
  195.  
  196.  
  197.  
  198. <p>The two ant tasks triggered in the JenkinsFile (distplugins and buildNsfs) are defined in a custom ant build.xml script that is committed to the repository. This script makes use of the functionality of the BuildXPages project.</p>
  199.  
  200.  
  201.  
  202. <h3 class="wp-block-heading">Building Plugins</h3>
  203.  
  204.  
  205.  
  206. <p>The distplugins task is just used to sequence some other tasks in the script</p>
  207.  
  208.  
  209.  
  210. <figure class="wp-block-image"><img loading="lazy" decoding="async" width="771" height="58" src="https://camerongregor.com/wp-content/uploads/2019/06/image-3.png" alt="" class="wp-image-593" srcset="https://camerongregor.com/wp-content/uploads/2019/06/image-3.png 771w, https://camerongregor.com/wp-content/uploads/2019/06/image-3-300x23.png 300w, https://camerongregor.com/wp-content/uploads/2019/06/image-3-768x58.png 768w" sizes="(max-width: 771px) 100vw, 771px" /></figure>
  211.  
  212.  
  213.  
  214. <figure class="wp-block-image"><img loading="lazy" decoding="async" width="534" height="234" src="https://camerongregor.com/wp-content/uploads/2019/06/image-4.png" alt="" class="wp-image-594" srcset="https://camerongregor.com/wp-content/uploads/2019/06/image-4.png 534w, https://camerongregor.com/wp-content/uploads/2019/06/image-4-300x131.png 300w" sizes="(max-width: 534px) 100vw, 534px" /><figcaption>This shows the copyPlugins task which simply copies the source of the plugins to a working directory in  preparation to be built by Eclipse PDE Build&#8217;s Headless plugin/feature building.</figcaption></figure>
  215.  
  216.  
  217.  
  218. <figure class="wp-block-image"><img loading="lazy" decoding="async" width="731" height="277" src="https://camerongregor.com/wp-content/uploads/2019/06/image-5.png" alt="" class="wp-image-595" srcset="https://camerongregor.com/wp-content/uploads/2019/06/image-5.png 731w, https://camerongregor.com/wp-content/uploads/2019/06/image-5-300x114.png 300w, https://camerongregor.com/wp-content/uploads/2019/06/image-5-1320x500.png 1320w" sizes="(max-width: 731px) 100vw, 731px" /><figcaption>This is the buildPlugins custom task which calls the BuildXPages buildPlugins task, this is where Eclipse PDE Build does it&#8217;s thing.</figcaption></figure>
  219.  
  220.  
  221.  
  222. <figure class="wp-block-image"><img loading="lazy" decoding="async" width="396" height="53" src="https://camerongregor.com/wp-content/uploads/2019/06/image-6.png" alt="" class="wp-image-596" srcset="https://camerongregor.com/wp-content/uploads/2019/06/image-6.png 396w, https://camerongregor.com/wp-content/uploads/2019/06/image-6-300x40.png 300w" sizes="(max-width: 396px) 100vw, 396px" /><figcaption>After building the plugins I unzip them because they are zipped up after PDE build does it&#8217;s thing.</figcaption></figure>
  223.  
  224.  
  225.  
  226. <figure class="wp-block-image"><img loading="lazy" decoding="async" width="508" height="166" src="https://camerongregor.com/wp-content/uploads/2019/06/image-7.png" alt="" class="wp-image-597" srcset="https://camerongregor.com/wp-content/uploads/2019/06/image-7.png 508w, https://camerongregor.com/wp-content/uploads/2019/06/image-7-300x98.png 300w" sizes="(max-width: 508px) 100vw, 508px" /><figcaption>Lastly, I copy the newly build plugins to an  &#8216;update site&#8217; so I always have the latest plugins available in the same place on the file system. This is where I will deploy them from when we deploy</figcaption></figure>
  227.  
  228.  
  229.  
  230. <h3 class="wp-block-heading">Building NSFs</h3>
  231.  
  232.  
  233.  
  234. <p>The buildNsfs task coordinates the building of all the NSFs</p>
  235.  
  236.  
  237.  
  238. <figure class="wp-block-image"><img loading="lazy" decoding="async" width="579" height="377" src="https://camerongregor.com/wp-content/uploads/2019/06/image-8.png" alt="" class="wp-image-598" srcset="https://camerongregor.com/wp-content/uploads/2019/06/image-8.png 579w, https://camerongregor.com/wp-content/uploads/2019/06/image-8-300x195.png 300w" sizes="(max-width: 579px) 100vw, 579px" /></figure>
  239.  
  240.  
  241.  
  242. <p>The &#8216;nsfbuild&#8217; is a macro that is defined within the same custom build.xml ant script, this calls the &#8216;buildnsf&#8217; task from the BuildXPages project.</p>
  243.  
  244.  
  245.  
  246. <figure class="wp-block-image"><img loading="lazy" decoding="async" width="995" height="411" src="https://camerongregor.com/wp-content/uploads/2019/06/image-9.png" alt="" class="wp-image-599" srcset="https://camerongregor.com/wp-content/uploads/2019/06/image-9.png 995w, https://camerongregor.com/wp-content/uploads/2019/06/image-9-300x124.png 300w, https://camerongregor.com/wp-content/uploads/2019/06/image-9-768x317.png 768w" sizes="(max-width: 995px) 100vw, 995px" /></figure>
  247.  
  248.  
  249.  
  250. <p>If there are any errors in the NSF build, they will show in the console output of the Jenkins build</p>
  251.  
  252.  
  253.  
  254. <figure class="wp-block-image"><img loading="lazy" decoding="async" width="864" height="322" src="https://camerongregor.com/wp-content/uploads/2019/06/image-10.png" alt="" class="wp-image-600" srcset="https://camerongregor.com/wp-content/uploads/2019/06/image-10.png 864w, https://camerongregor.com/wp-content/uploads/2019/06/image-10-300x112.png 300w, https://camerongregor.com/wp-content/uploads/2019/06/image-10-768x286.png 768w" sizes="(max-width: 864px) 100vw, 864px" /></figure>
  255.  
  256.  
  257.  
  258. <p>If there were no errors during plugins and NSF building, then we get a green status and we are ready to deploy!</p>
  259.  
  260.  
  261.  
  262. <figure class="wp-block-image"><img loading="lazy" decoding="async" width="886" height="213" src="https://camerongregor.com/wp-content/uploads/2019/06/image-11.png" alt="" class="wp-image-601" srcset="https://camerongregor.com/wp-content/uploads/2019/06/image-11.png 886w, https://camerongregor.com/wp-content/uploads/2019/06/image-11-300x72.png 300w, https://camerongregor.com/wp-content/uploads/2019/06/image-11-768x185.png 768w" sizes="(max-width: 886px) 100vw, 886px" /></figure>
  263.  
  264.  
  265.  
  266. <p>As you can see, our build takes about 4 mins. Our NSFs&#8217; are building incrementally thanks to the Headless Designer Plugin that comes with BuildXPages project. Otherwise, a full-build of every NSF would be performed every time, and this would take much longer.</p>
  267.  
  268.  
  269.  
  270. <h2 class="wp-block-heading">Deploying!</h2>
  271.  
  272.  
  273.  
  274. <p>We have 2 Jenkins jobs for deploying, one to upload our plugins to the NSF update site, and another one to run the design refresh of the templates on the production server.</p>
  275.  
  276.  
  277.  
  278. <p>Our servers are set to restart every morning at 6am, so we actually schedule these 2 tasks to run every morning at 5:15am for plugins and then 5:30am for templates refresh. </p>
  279.  
  280.  
  281.  
  282. <p>We used to just restart after the 1am design refresh but we had some people from other offices still using our server at that time, so 6am was a better time for our company.</p>
  283.  
  284.  
  285.  
  286. <h3 class="wp-block-heading">Import Master Plugins</h3>
  287.  
  288.  
  289.  
  290. <p>The Import master plugins is just a custom Jenkins job that is not linked to any source repository, it simply has one file in the job that is the ant script.</p>
  291.  
  292.  
  293.  
  294. <p>The ant script uses the BuildXPages <strong>importplugins </strong>task to import the plugins into an Update site nsf that is on the production server, from the plugins final location in the earlier build task.</p>
  295.  
  296.  
  297.  
  298. <h3 class="wp-block-heading">Refresh Data Templates</h3>
  299.  
  300.  
  301.  
  302. <p>All the NSFs that are &#8216;built&#8217; in the Headless Domino Designer have a template name with a suffix of  &#8216;_master&#8217; to indicate they are the master branch design. These NSFs are considered to be &#8216;local&#8217; to the Jenkins machine. </p>
  303.  
  304.  
  305.  
  306. <p>All the production templates are set to inherit from their templates with suffix &#8216;_master&#8217;.</p>
  307.  
  308.  
  309.  
  310. <p>So for refreshing the production templates, we simply use the BuildXPages refreshdbdesign task to refresh from our &#8216;local&#8217; templates to the production server.</p>
  311.  
  312.  
  313.  
  314. <figure class="wp-block-image"><img loading="lazy" decoding="async" width="804" height="326" src="https://camerongregor.com/wp-content/uploads/2019/06/image-12.png" alt="" class="wp-image-602" srcset="https://camerongregor.com/wp-content/uploads/2019/06/image-12.png 804w, https://camerongregor.com/wp-content/uploads/2019/06/image-12-300x122.png 300w, https://camerongregor.com/wp-content/uploads/2019/06/image-12-768x311.png 768w" sizes="(max-width: 804px) 100vw, 804px" /></figure>
  315.  
  316.  
  317.  
  318. <h2 class="wp-block-heading">Conclusion</h2>
  319.  
  320.  
  321.  
  322. <p>Hopefully that was a good overview on how we are managing and deploying our xpages applications using BuildXPages + Jenkins. I&#8217;m sure I missed something here or there so let me know if you have any questions!</p>
  323. ]]></content:encoded>
  324. <wfw:commentRss>https://camerongregor.com/2019/06/19/our-current-deployment-setup-github-jenkins-buildxpages/feed/</wfw:commentRss>
  325. <slash:comments>2</slash:comments>
  326. </item>
  327. <item>
  328. <title>Minor enhancements to the XPages Table Control</title>
  329. <link>https://camerongregor.com/2017/12/13/minor-enhancements-to-the-xpages-table-control/</link>
  330. <comments>https://camerongregor.com/2017/12/13/minor-enhancements-to-the-xpages-table-control/#comments</comments>
  331. <dc:creator><![CDATA[camerongregor]]></dc:creator>
  332. <pubDate>Wed, 13 Dec 2017 10:44:04 +0000</pubDate>
  333. <category><![CDATA[Software Development]]></category>
  334. <category><![CDATA[control]]></category>
  335. <category><![CDATA[extlibx]]></category>
  336. <category><![CDATA[xpages]]></category>
  337. <guid isPermaLink="false">http://camerongregor.com/?p=566</guid>
  338.  
  339. <description><![CDATA[As mentioned in my previous post, I have started up a fork of the ExtLibX project and have started contributing to it instead of my previous GregorbyteXspLibrary. Last week I shared the JDBC Configuration&#46;&#46;&#46;]]></description>
  340. <content:encoded><![CDATA[<p>As mentioned in <a href="https://camerongregor.com/2017/12/01/my-new-approach-for-sharing-xpages-controls-and-extensions/" target="_blank" rel="noopener">my previous post</a>, I have started up a fork of the ExtLibX project and have started contributing to it instead of my previous GregorbyteXspLibrary. Last week I shared the <a href="https://camerongregor.com/2017/12/05/jdbcnotesdocuments/" target="_blank" rel="noopener">JDBC Configuration Provider</a> which uses Notes Documents to store the configuration info,  within this release was another small contribution with 2 minor enhancements for the XPages  <strong>table</strong> control.</p>
  341. <p>The core XPages table control does not provide support for <em>thead</em>, <em>tbody</em>, and <em>tfoot</em> elements. Also there isn&#8217;t an option for a <em>th</em> control.</p>
  342. <p>There are some easy workarounds for this such as just <a href="http://www.dominoguru.com/pages/ibm_xpages_xptable_using_passthru_markup.html" target="_blank" rel="noopener">using passthrough html</a> as Chris Toohey demonstrated on his blog.</p>
  343. <p>This passthru technique works fine but I just thought I would share my approach, which was to add support for these elements by creating an extra <strong>th</strong> control, and also extending the table renderer to support use of thead, tfoot and tbody.</p>
  344. <p>It isn&#8217;t that exciting but I just thought it was a good example of being able to extend core controls, and add your own desired functionality to them, or modifying existing functionality.</p>
  345. <p>If you would like to try these modifications out, then <a href="https://github.com/camac/XPagesExtLibX/releases" target="_blank" rel="noopener">download the latest release</a> from my camac/ExtLibX project and install to both domino designer and your target domino server.</p>
  346. <p>If you don&#8217;t want to install plugins, you should also be able to take the table renderer and th component source code from within <a href="https://github.com/OpenNTF/XPagesExtLibX/commit/145e5ccffd4d49e184c5c9b7295560769dec29c6" target="_blank" rel="noopener">this commit</a> and install it to the java, faces-config and xsp-config files in your nsf</p>
  347. <h2>Using the Table Header Cell control</h2>
  348. <p>To use a th Cell, simply use the Table Header Cell control. It is exactly like a normal <code>&lt;xp:td&gt;</code> control, except it will render as <em>th</em> instead of <em>td</em></p><pre class="urvanov-syntax-highlighter-plain-tag">&lt;xp:tr&gt;
  349.    &lt;!-- Using the th control --&gt;
  350.    &lt;xe:th&gt;Last Name&lt;/xe:th&gt;
  351.    &lt;xe:th&gt;First Name&lt;/xe:th&gt;
  352. &lt;/xp:tr&gt;</pre><p></p>
  353. <h2>Using Table Header, Footer and Body sections</h2>
  354. <p>Support for &lt;thead&gt;, &lt;tfoot&gt; and &lt;tbody&gt; elements is provided by an extended version of the Table Renderer, this renderer&#8217;s rendererType is: <strong>com.ibm.xsp.extlibx.Table</strong>.</p>
  355. <p>You have 2 choices on how to enable the table renderer</p>
  356. <ul>
  357. <li>Make the renderer default for all tables in the application</li>
  358. <li>Enable the renderer only for specific tables</li>
  359. </ul>
  360. <h3>Setting Table Renderer for All Tables</h3>
  361. <p>You can set the renderer Type for all Tables in your application by including the following in your theme configuration file:</p><pre class="urvanov-syntax-highlighter-plain-tag">&lt;control&gt;
  362.    &lt;name&gt;HtmlTable&lt;/name&gt;
  363.    &lt;property&gt;
  364.        &lt;name&gt;rendererType&lt;/name&gt;
  365.        &lt;value&gt;com.ibm.xsp.extlibx.Table&lt;/value&gt;
  366.    &lt;/property&gt;
  367. &lt;/control&gt;</pre><p></p>
  368. <h3>Setting RendererType for a specific table</h3>
  369. <p>To enable the renderer for specific tables, just set the rendererType to <strong>com.ibm.xsp.extlibx.Table</strong></p><pre class="urvanov-syntax-highlighter-plain-tag">&lt;xp:table rendererType="com.ibm.xsp.extlibx.Table"&gt;</pre><p></p>
  370. <h3>How to specify thead, tfoot and tbody</h3>
  371. <p>The thead and tfoot elements can be used by providing a facet named thead or tfoot<br />
  372. The tbody element will be rendered by default and contain all the child elements of the table</p><pre class="urvanov-syntax-highlighter-plain-tag">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
  373. &lt;xp:view xmlns:xp="http://www.ibm.com/xsp/core" xmlns:xe="http://www.ibm.com/xsp/coreex"&gt;
  374.  
  375.    &lt;xp:table rendererType="com.ibm.xsp.extlibx.Table"&gt;
  376. &lt;!-- thead and tfoot are specified using facets --&gt;
  377. &lt;xp:this.facets&gt;
  378.    &lt;xp:tr xp:key="thead"&gt;
  379.    &lt;xe:th&gt;Header 1&lt;/xe:th&gt;
  380.    &lt;xe:th&gt;Header 2&lt;/xe:th&gt;
  381.    &lt;/xp:tr&gt;
  382.    &lt;xp:tr xp:key="tfoot"&gt;
  383.    &lt;xp:td&gt;Footer 1&lt;/xp:td&gt;
  384.    &lt;xp:td&gt;Footer 2&lt;/xp:td&gt;
  385.    &lt;/xp:tr&gt;
  386.  
  387. &lt;/xp:this.facets&gt;
  388. &lt;!-- all the child controls will be automatically renderer within a tbody element --&gt;
  389. &lt;xp:tr&gt;
  390.    &lt;xp:td&gt;R1C1&lt;/xp:td&gt;
  391.    &lt;xp:td&gt;R1C2&lt;/xp:td&gt;
  392. &lt;/xp:tr&gt;
  393. &lt;xp:tr&gt;
  394.    &lt;xp:td&gt;R2C1&lt;/xp:td&gt;
  395.        &lt;xp:td&gt;R2C2&lt;/xp:td&gt;
  396.    &lt;/xp:tr&gt;
  397.    &lt;/xp:table&gt;
  398.  
  399. &lt;/xp:view&gt;</pre><p></p>
  400. <h3>Multiple rows within thead / tfoot</h3>
  401. <p>Chris Toohey makes a good point in the comments, sometimes you want more than one row in the header or footer. You can achieve this by using a panel as the root component instead of the tr.  You can then put multiple tr&#8217;s within that panel.</p>
  402. <p>The panel&#8217;s &lt;div&gt; tags won&#8217;t be rendered unless it has an id.  If it has an id and you don&#8217;t want the tags rendered you can also use <code>disableOutputTag="false"</code>.</p><pre class="urvanov-syntax-highlighter-plain-tag">&lt;xp:table&gt;
  403. &lt;xp:this.facets&gt;
  404. &lt;!-- Multiple rows within tfoot / thead --&gt;
  405. &lt;xp:panel xp:key="tfoot"&gt;
  406.  
  407. &lt;xp:tr&gt;
  408. &lt;xp:td&gt;&lt;/xp:td&gt;
  409. &lt;xp:td&gt;&lt;/xp:td&gt;
  410. &lt;/xp:tr&gt;
  411. &lt;xp:tr&gt;
  412. &lt;xp:td&gt;&lt;/xp:td&gt;
  413. &lt;xp:td&gt;&lt;/xp:td&gt;
  414. &lt;/xp:tr&gt;
  415.  
  416. &lt;/xp:panel&gt;
  417. &lt;/xp:this.facets&gt;
  418.  
  419. &lt;/xp:table&gt;</pre><p>&nbsp;</p>
  420. <p>Let me know what you think. I know this one wasn&#8217;t very exciting but I should have some better controls coming out soon! I just did this one because it was quick and easy.</p>
  421. ]]></content:encoded>
  422. <wfw:commentRss>https://camerongregor.com/2017/12/13/minor-enhancements-to-the-xpages-table-control/feed/</wfw:commentRss>
  423. <slash:comments>2</slash:comments>
  424. </item>
  425. <item>
  426. <title>XPages JDBC Configuration via NotesDocuments</title>
  427. <link>https://camerongregor.com/2017/12/05/jdbcnotesdocuments/</link>
  428. <comments>https://camerongregor.com/2017/12/05/jdbcnotesdocuments/#respond</comments>
  429. <dc:creator><![CDATA[camerongregor]]></dc:creator>
  430. <pubDate>Tue, 05 Dec 2017 11:34:46 +0000</pubDate>
  431. <category><![CDATA[Software Development]]></category>
  432. <category><![CDATA[extlibx]]></category>
  433. <category><![CDATA[jdbc]]></category>
  434. <category><![CDATA[xpages]]></category>
  435. <guid isPermaLink="false">http://camerongregor.com/?p=550</guid>
  436.  
  437. <description><![CDATA[If you have used the Extension Library&#8217;s Relational controls, then you are probably familiar with the process of configuring your JDBC Connection details. You have the option of setting up a &#8216;named&#8217; connection so&#46;&#46;&#46;]]></description>
  438. <content:encoded><![CDATA[<p>If you have used the Extension Library&#8217;s Relational controls, then you are probably familiar with the process of configuring your JDBC Connection details.</p>
  439. <p>You have the option of setting up a &#8216;named&#8217; connection so you can reference a database connection by name. This involves creating an xml file <strong>&lt;yourconnection&gt;.jdbc</strong> in the <strong>WebContent\WEB-INF\jdbc\ </strong>directory. This xml file specifies the driver, url, username, password, and perhaps some information about connection pooling.</p>
  440. <p>Alternatively your can just build your own connection url and use this directly in your Java code or XPages controls.</p>
  441. <p>Named connections are a much nicer way to go, and they allow for connection pooling, however the method of using the xml file within the WEB-INF\jdbc directory is a little annoying.</p>
  442. <p>This is not a very accessible or flexible way to manage your connection information. Often your local development SQL Database will have different username / password / url etc. So you often end up changing the config files back and forth everytime you want to develop and deploy.</p>
  443. <p>So I decided to come up with a way to store JDBC Connection information somewhere else, and it seemed logical to store it in NotesDocuments. This way the JDBC connections are configurable at run-time and do not require a change to a design.</p>
  444. <p>Additionally, these JDBC configuration documents can be protected from read-access so that only admins and database signer&#8217;s can access them. The session as signer should be able to access them with elevated rights.</p>
  445. <p><strong>Short Demonstration Video</strong></p>
  446. <p>If you  would like to see the JDBC Provider in action please have a look at this video</p>
  447. <div class="video-container"><iframe loading="lazy" title="XPages JDBC Configuration from Notes Documents" width="500" height="281" src="https://www.youtube.com/embed/7sassOh-wkw?feature=oembed&#038;wmode=opaque" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe></div>
  448. <h3></h3>
  449. <h3>How can I use this JDBCNotesDocumentProvider system?</h3>
  450. <p>Download the <a href="https://github.com/camac/XPagesExtLibX/releases/tag/v9.0.1.v00_17-jdbcprovideralpha" target="_blank" rel="noopener">Alpha Release</a> in my fork of the ExtLibX project and have a look at the documentation in the PDF in the release. You can also have a look in the demo video above.</p>
  451. <p>As mentioned in my previous post, <a href="https://camerongregor.com/2017/12/01/my-new-approach-for-sharing-xpages-controls-and-extensions/" target="_blank" rel="noopener">I have decided to release all my future contributions through the ExtLibX project</a>.</p>
  452. <p>If this solution is working for you then you should be fine to use it in production but I am just calling it a alpha release because I am looking to get some feedback before making a final &#8216;release&#8217;.</p>
  453. <h3>How does the JDBCNotesDocumentProvider work?</h3>
  454. <p>I basically copied the way the Extension Library initializes it&#8217;s JDBC Information from the xml files in the WEB-INF/jdbc folder, and modified it to instead look in NotesDocuments! Sounds simple but there was a little bit of fiddling around.</p>
  455. <p>Basically:</p>
  456. <ul>
  457. <li>Someone Access the XPages application</li>
  458. <li>The JDBC Configuration Initialization is triggered
  459. <ul>
  460. <li>If it is set to use &#8216;local&#8217; configuration it will search the current database for JDBCConnection info</li>
  461. <li>If it is set to use &#8216;global&#8217; configuration it will search the central configuration database for JDBCConnection info</li>
  462. </ul>
  463. </li>
  464. </ul>
  465. <p>Actually in my original solution, I was using an &#8216;ApplicationInitializer&#8217; however this runs <em>before </em>you have access to sessionAsSigner, so this was a bit of a problem. I had trouble getting a full-access session but managed to do it with some trickery, however I don&#8217;t think this &#8216;trickery&#8217; is suitable to be shared, so I came up with an alternative.</p>
  466. <p>I moved the initialization code to a PhaseListener, and set it up so the initialization will only run if it hasn&#8217;t been successfully done yet. I still don&#8217;t like this so much but it works.</p>
  467. <h3>Feedback / Comments</h3>
  468. <p>Would love to hear if this is something you think you would use. If you have any suggestions / improvements please let me know!</p>
  469. <p>&nbsp;</p>
  470. ]]></content:encoded>
  471. <wfw:commentRss>https://camerongregor.com/2017/12/05/jdbcnotesdocuments/feed/</wfw:commentRss>
  472. <slash:comments>0</slash:comments>
  473. </item>
  474. <item>
  475. <title>My New Approach for Sharing XPages Controls and Extensions</title>
  476. <link>https://camerongregor.com/2017/12/01/my-new-approach-for-sharing-xpages-controls-and-extensions/</link>
  477. <comments>https://camerongregor.com/2017/12/01/my-new-approach-for-sharing-xpages-controls-and-extensions/#comments</comments>
  478. <dc:creator><![CDATA[camerongregor]]></dc:creator>
  479. <pubDate>Fri, 01 Dec 2017 12:33:41 +0000</pubDate>
  480. <category><![CDATA[Software Development]]></category>
  481. <category><![CDATA[extlib]]></category>
  482. <category><![CDATA[xpages]]></category>
  483. <guid isPermaLink="false">http://camerongregor.com/?p=553</guid>
  484.  
  485. <description><![CDATA[Over the past few years I have shared a handful of controls and techniques on my blog, and most of these I have made available on github in one way or another. Originally I&#46;&#46;&#46;]]></description>
  486. <content:encoded><![CDATA[<p>Over the past few years I have shared a handful of controls and techniques on my blog, and most of these I have made available on github in one way or another. Originally I was creating a separate github project for each one such as <a href="https://github.com/camac/XPagesEmailValidatorPlugin" target="_blank" rel="noopener">EmailValidator</a>, <a href="https://github.com/camac/XPagesPhoneNumberControl" target="_blank" rel="noopener">PhoneNumberControl</a> and the <a href="https://github.com/camac/XPagesMessagesControl" target="_blank" rel="noopener">Messages Controls.</a> After these few I decided it would be more efficient to create a single project that I could put them all into, so I then began to contribute them via my <a href="https://github.com/camac/GregorbyteXspLibrary" target="_blank" rel="noopener">GregorbyteXspLibrary</a>.</p>
  487. <p>From time to time I had a few comments like &#8220;This would be a nice addition to the Extension Library&#8221;. However whenever I found time to work on open source projects, it always seemed to be a higher priority to work on sharing something new instead of doing more work on something I had already shared.</p>
  488. <p>It would mean the extra step of moving the work from GregorbyteXspLibrary into the ExtensionLibrary, refactoring package names, testing it all again, updating copyright licensing, and then creating a pull request to have it merged into the Extension Library.</p>
  489. <p>Even after that there would be no guarantee of pull requests being accepted into the OpenNTF repository. So the work maybe would just sit there and seem like a wasted effort. I thought &#8220;well maybe people would be still be able to install my &#8216;fork&#8217; of the Extension Library?&#8221;, but then I could see how this get messy when future IBM releases come out with newer Extension Library versions.</p>
  490. <p>The short story: I just wasn&#8217;t feeling that clear on how I should approach it so I just left it as it was.</p>
  491. <h3>What about ExtLibX?</h3>
  492. <p>I had totally forgotten about ExtLibX.</p>
  493. <p>ExtLib is the main Extension Library that we all know and love, but there is also the ExtLib<strong>X</strong> which is the &#8216;e<strong>X</strong>perimental&#8217; Extension Library.</p>
  494. <p>The ExtLibX project was created exactly for the purpose of submitting experimental controls. It is a bonus extension library that people have the option of installing alongside the main Extension Library.</p>
  495. <p>The idea is that after the experimental controls have gained acceptance as a &#8220;good idea&#8221; they could be migrated into the main Extension Library.</p>
  496. <p>The ExtLibX originally contained things like the Relational controls (which are now part of the core ExtLib), and currently has some work sitting in it for Bootstrap4.</p>
  497. <p>So now that I remember about ExtLibX, I have finally come up with a strategy for sharing controls that I am confident in.</p>
  498. <h2>My New Strategy for Sharing Reusable Controls and Techniques!</h2>
  499. <ul>
  500. <li>My existing <a href="https://github.com/camac/GregorbyteXspLibrary" target="_blank" rel="noopener">camac/GregorbyteXspLibrary</a> will remain as it stands now, with no further development</li>
  501. <li>I&#8217;ve created my own fork of the experimental extension library: <a href="https://github.com/camac/XPagesExtLibX" target="_blank" rel="noopener">camac/XPagesExtLibX</a></li>
  502. <li>I&#8217;ll share all my new controls and techniques through camac/XPagesExtLibX</li>
  503. <li>I&#8217;ll prepare install-able releases so that people can use the controls straight away if they want to.  This will hopefully help me get some feedback / bug reports / suggested improvements.</li>
  504. <li>If I get feedback that a control or technique is useful and is working without problems, I will prepare a pull request for that control so that it can be reviewed for acceptance into the main <a href="https://github.com/OpenNTF/XPagesExtLibX" target="_blank" rel="noopener">OpenNTF/XPagesExtLibX</a> project.</li>
  505. <li>If IBM decide that control is useful to everyone, they are then able to migrate it from ExtLibX to ExtLib  at there own desire as they have access to both ExtLibX and ExtLib</li>
  506. <li>When I get time I will move the controls from GregorbyteXspLibrary over to ExtLibX</li>
  507. </ul>
  508. <p>Overall I am pretty happy with this new strategy as enables me to share controls so that they are immediately usable, but also still have a pathway to <em>possibly</em> be included in the ExtensionLibrary.</p>
  509. <p>I am hoping to share a few controls and techniques in the next few months that will be a bit more useful for day-to-day XPages work, and also a few extended versions of some standard controls that make them a little bit more developer friendly.</p>
  510. <p>I&#8217;m hoping to release the first version in the next week which should contain</p>
  511. <ul>
  512. <li>Some modified Table Controls
  513. <ul>
  514. <li>Modified table renderer to have proper <code>&lt;thead&gt; &lt;tfoot&gt; &lt;tbody&gt;</code> structure.</li>
  515. <li><code>&lt;th&gt;</code> control</li>
  516. </ul>
  517. </li>
  518. <li>Master / Slave checkbox controls
  519. <ul>
  520. <li>easy bulk record selection within repeat controls</li>
  521. <li>select all / deselect all functionality</li>
  522. <li>automatic row-styling based on selected checkbox</li>
  523. <li>ability to use the whole row as a click target for that row&#8217;s checkbox</li>
  524. </ul>
  525. </li>
  526. <li>A JDBC Connection provider that loads configuration information from Notes Documents in a config database (either current database or centralised database)</li>
  527. </ul>
  528. <p>If your interested in any of these above please let me know in the comments below!<br />
  529. Do you have any other ideas for other Controls? I have a few more in the pipeline. Let&#8217;s see if we can breathe a bit of life into the Extension Library!</p>
  530. ]]></content:encoded>
  531. <wfw:commentRss>https://camerongregor.com/2017/12/01/my-new-approach-for-sharing-xpages-controls-and-extensions/feed/</wfw:commentRss>
  532. <slash:comments>2</slash:comments>
  533. </item>
  534. <item>
  535. <title>Embedded Experiences not rendering after IBM Notes FP9</title>
  536. <link>https://camerongregor.com/2017/09/27/embedded-experiences-not-rendering-after-ibm-notes-fp9/</link>
  537. <comments>https://camerongregor.com/2017/09/27/embedded-experiences-not-rendering-after-ibm-notes-fp9/#comments</comments>
  538. <dc:creator><![CDATA[camerongregor]]></dc:creator>
  539. <pubDate>Wed, 27 Sep 2017 05:36:43 +0000</pubDate>
  540. <category><![CDATA[Software Development]]></category>
  541. <category><![CDATA[embeddedexperience]]></category>
  542. <category><![CDATA[FP9]]></category>
  543. <category><![CDATA[notes]]></category>
  544. <guid isPermaLink="false">http://camerongregor.com/?p=535</guid>
  545.  
  546. <description><![CDATA[Embedded Experiences are no longer rendering after upgrading to FP9. I suspect it is something to do with the changes to Embedded Browser, either removing it or upgrading it or something else. I haven&#8217;t&#46;&#46;&#46;]]></description>
  547. <content:encoded><![CDATA[<p>Embedded Experiences are no longer rendering after upgrading to FP9.</p>
  548. <p>I suspect it is something to do with the changes to Embedded Browser, either removing it or upgrading it or something else.</p>
  549. <p>I haven&#8217;t had a chance to investigate fully. Perhaps there may be a workaround, or perhaps this is specific to the way we are using embedded experiences.</p>
  550. <p>We use embedded experiences quite a bit at our company so we have rolled back for now. If I figure anything out I will post an update but just be aware before upgrading if you are using embedded experiences to test FP9 in your situation before rolling out.</p>
  551. <p>The trace log shows the following stack traces.</p>
  552. <p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-541" src="https://camerongregor.com/wp-content/uploads/2017/09/EmbeddedExperienceBug1.png" alt="" width="798" height="573" srcset="https://camerongregor.com/wp-content/uploads/2017/09/EmbeddedExperienceBug1.png 798w, https://camerongregor.com/wp-content/uploads/2017/09/EmbeddedExperienceBug1-300x215.png 300w, https://camerongregor.com/wp-content/uploads/2017/09/EmbeddedExperienceBug1-768x551.png 768w" sizes="(max-width: 798px) 100vw, 798px" /></p>
  553. <p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-542" src="https://camerongregor.com/wp-content/uploads/2017/09/EmbeddedExperienceBug2.png" alt="" width="786" height="675" srcset="https://camerongregor.com/wp-content/uploads/2017/09/EmbeddedExperienceBug2.png 786w, https://camerongregor.com/wp-content/uploads/2017/09/EmbeddedExperienceBug2-300x258.png 300w, https://camerongregor.com/wp-content/uploads/2017/09/EmbeddedExperienceBug2-768x660.png 768w" sizes="(max-width: 786px) 100vw, 786px" /></p>
  554. ]]></content:encoded>
  555. <wfw:commentRss>https://camerongregor.com/2017/09/27/embedded-experiences-not-rendering-after-ibm-notes-fp9/feed/</wfw:commentRss>
  556. <slash:comments>2</slash:comments>
  557. </item>
  558. <item>
  559. <title>BuildXPages Deployment Automation Tools &#8211; v1.0.0</title>
  560. <link>https://camerongregor.com/2017/09/21/buildxpages-deployment-automation-tools-v1-0-0/</link>
  561. <comments>https://camerongregor.com/2017/09/21/buildxpages-deployment-automation-tools-v1-0-0/#comments</comments>
  562. <dc:creator><![CDATA[camerongregor]]></dc:creator>
  563. <pubDate>Thu, 21 Sep 2017 12:47:18 +0000</pubDate>
  564. <category><![CDATA[Software Development]]></category>
  565. <category><![CDATA[ant]]></category>
  566. <category><![CDATA[deployment]]></category>
  567. <category><![CDATA[designer]]></category>
  568. <category><![CDATA[headless]]></category>
  569. <category><![CDATA[plugin]]></category>
  570. <guid isPermaLink="false">http://camerongregor.com/?p=529</guid>
  571.  
  572. <description><![CDATA[After quite a bit of work I have finally published the first release of BuildXPages! What is BuildXPages? BuildXPages is a project which is useful if you are interested in Automating tasks that are&#46;&#46;&#46;]]></description>
  573. <content:encoded><![CDATA[<p>After quite a bit of work I have finally published the <a href="https://github.com/camac/BuildXPages/releases/tag/v1.0.0" target="_blank" rel="noopener">first release of BuildXPages!</a></p>
  574. <h2>What is BuildXPages?</h2>
  575. <div>
  576. <p>BuildXPages is a project which is useful if you are interested in Automating tasks that are involved in building XPages.</p>
  577. </div>
  578. <div>
  579. <p>For example you may be interested in automating some of these tasks:</p>
  580. </div>
  581. <div>
  582. <ul>
  583. <li>Building NSFs from an On-Disk Project</li>
  584. <li>Building Plugins and Features</li>
  585. <li>Refreshing NSF Designs</li>
  586. <li>Setting Template Inheritance of NSFs</li>
  587. <li>Start, Stop and Restart Http server</li>
  588. <li>Uploading Plugins to an NSF Update Site</li>
  589. <li>Deploy Plugins to Notes/Designer</li>
  590. </ul>
  591. </div>
  592. <div>
  593. <p>BuildXPages provides tools which can be used to achieve these tasks. The main artifact of the project is a collection of tasks that can be used in an Ant build script, but the project also includes a Plugin for Domino Designer to facilitate the building of NSFs.</p>
  594. <h2>Project Background</h2>
  595. </div>
  596. <p>Automating the build and deployment of any software application is a great idea to be able achieve the same result every time. Information and tools for doing this in XPages is relatively scarce but you can find blog posts and some tools out there.</p>
  597. <p>I have written a few years ago about the <a href="https://camerongregor.com/2014/08/09/build-system-for-xpages-and-osgi-plugins/" target="_blank" rel="noopener">build system for XPages</a> we use at my workplace, and have shared my slides from the <a href="https://camerongregor.com/2015/06/11/build-automation-for-xpages-presentation-slides-ausluginform-2015/" target="_blank" rel="noopener">talk I gave at AUSLUG 2015</a> so have a look at those for some background (be aware these posts are old and I may be doing some things differently now), I decided to gather the tools that I have developed to support this and make them available under it&#8217;s own Open Source Project.</p>
  598. <p>If you look you will see the project has actually been on Github since 2014! But the project never had any good documentation and some of the parts where not very friendly to use.</p>
  599. <p>Over the last couple of months I have been working away on bringing the project up to a standard where it can be understood and used by others. This includes things like:</p>
  600. <ul>
  601. <li>making information and error messages more useful and understandable</li>
  602. <li>making task names and arguments more user-friendly</li>
  603. <li>replacing my hard-coded options with configurable options</li>
  604. <li>most of all .. writing documentation!</li>
  605. </ul>
  606. <h2>Documentation</h2>
  607. <p>You can always find the latest Documentation on the <a href="http://camac.github.io/BuildXPages/" target="_blank" rel="noopener">Github Project&#8217;s Documentation website</a>. The documentation is also contained as PDF and HTML in the release download.</p>
  608. <p>A large part of writing the documentation was trying to figure out how to explain getting your environment setup. Whilst I think I have everything covered there is a every chance I have missed something that may be different for your environment, so if you do try it out and it doesn&#8217;t work please don&#8217;t hesitate to contact me.</p>
  609. <h3>Asciidoctor FTW!</h3>
  610. <p>Documentation for the  Project is written Asciidoctor using AsciidoctFX and generated using the ant-asciidoctorj plugin. The Asciidoctor &#8216;toolchain&#8217; is awesome and deserves it&#8217;s own blog post / video but that will have to be another day. There is already plenty of information out there about it. If you are going to write any documentation I highly recommend having a look at using it and feel free to ask me about it here or in the <a href="https://xpages-slack.herokuapp.com/" target="_blank" rel="noopener">XPages Slack chat</a>. You can see the &#8216;recipe&#8217; I use for generating the documentation by looking at the build.xml file in the root of the project, and you can see all the documentation &#8216;source&#8217; files in the doc folder.</p>
  611. <h3>Demonstration Tutorials</h3>
  612. <p>The Project contains some demonstration tutorials which are designed to verify that you have everything set up and to demonstrate the very basic tasks.</p>
  613. <h2>Feedback / Problems / Suggestions</h2>
  614. <p>I you have any problems with these let me know and I can double check what may be the problem either in your setup, or maybe a bug in the project.</p>
  615. <h2>A note about Build Tools</h2>
  616. <p>Part of your automation process is deciding what &#8216;build tool&#8217; you will use, which is usually program that helps coordinate a sequence of tasks that need to be done in order to build / deploy your project. This could be as simple as a batch file, or use a well developed system specifically for this purpose.</p>
  617. <p>I chose Ant because of it&#8217;s simplicity and availability of good documentation. It was very easy for me to get up and running and there were many examples out there to follow. I had also tried to give maven a go but found it hard to find good documentation and after a few dead ends just gave up and returned to making progress with ant.</p>
  618. <p>So with Ant as my choice I have written all these custom tasks to work with Ant. Ant is written in Java and so all the tasks are java based, Maven and Gradle are both Java based and I know there are other XPages developers who prefer maven or gradle. If anyone is interested in translating these tasks to be used as a plugin for gradle or maven plugin then I would be happy to help you adapt them and hopefully include them in this project so that others can also use.</p>
  619. <p>Also: if you are using maven be aware there is already the <a href="https://guedebyte.blog/2016/03/26/building-nsf-using-the-maven-headlessdesigner-plugin-from-openntf/" target="_blank" rel="noopener">headlessdesigner-maven-plugin</a> which is written about on Christian Guedemann&#8217;s blog</p>
  620. ]]></content:encoded>
  621. <wfw:commentRss>https://camerongregor.com/2017/09/21/buildxpages-deployment-automation-tools-v1-0-0/feed/</wfw:commentRss>
  622. <slash:comments>1</slash:comments>
  623. </item>
  624. <item>
  625. <title>Generating and Downloading Files Using XPages&#8217; Persistence Service</title>
  626. <link>https://camerongregor.com/2017/09/13/generating-and-downloading-files-using-xpages-persistence-service/</link>
  627. <comments>https://camerongregor.com/2017/09/13/generating-and-downloading-files-using-xpages-persistence-service/#comments</comments>
  628. <dc:creator><![CDATA[camerongregor]]></dc:creator>
  629. <pubDate>Wed, 13 Sep 2017 11:42:23 +0000</pubDate>
  630. <category><![CDATA[Software Development]]></category>
  631. <category><![CDATA[download]]></category>
  632. <category><![CDATA[file]]></category>
  633. <category><![CDATA[persistence]]></category>
  634. <category><![CDATA[xpages]]></category>
  635. <guid isPermaLink="false">http://camerongregor.com/?p=517</guid>
  636.  
  637. <description><![CDATA[When developing an XPages application you may run into the need to generate files to be downloaded by the user. For example: Generating a PDF Report Exporting Data to Excel Spreadsheet Creating a Zip&#46;&#46;&#46;]]></description>
  638. <content:encoded><![CDATA[<p>When developing an XPages application you may run into the need to generate files to be downloaded by the user.</p>
  639. <p>For example:</p>
  640. <ul>
  641. <li>Generating a PDF Report</li>
  642. <li>Exporting Data to Excel Spreadsheet</li>
  643. <li>Creating a Zip File of several attachments</li>
  644. </ul>
  645. <p>Achieving these tasks usually raises 2 main problems to solve;</p>
  646. <ol>
  647. <li>What temporary place can I use to generate the files?</li>
  648. <li>How do I allow the user to download the generated files?</li>
  649. </ol>
  650. <p>For the temporary place problem, some common solution is to either generate the files to some &#8216;in memory&#8217; representation or alternatively use the Java Temporary Directory. For the downloading problem, a common solution is to provide a button which calls a SSJS or Java bean method that hijacks the HttpResponse and writes out the appropriate headers and file contents.</p>
  651. <p>These solutions work fine, however built in to the XPages framework is the &#8216;Persistence Service&#8217; which is a mechanism for temporarily Persisting files that need to be available for download . Although this persistence service was built primarily for the standard operations of XPages, there is nothing stopping us from also enjoying the way it solves these 2 problems.</p>
  652. <h2>Why does the Persistence Service exist?</h2>
  653. <p>An advantage of Domino Databases is how easy it is to store Files and Images inside &#8216;Rich Text&#8217; Fields. When XPages needs to display these Embedded Images or allow Downloads of files from within the Document, then the user&#8217;s web browser will need to make request for these embedded objects using some sort of URL.</p>
  654. <p>Domino provides URLs which provide direct access to these embedded files and images, however these only provide access to the &#8216;saved&#8217; version of a document as it exists inside the Notes Database. These URLs are of no help for the &#8216;No mans land&#8217; period of time in which someone is creating a new document, or when editing an existing document and uploads a new File or Image but has not yet saved the document.</p>
  655. <p>When you open up Domino Document via XPages, the persistence service extracts the Document&#8217;s embedded images to a temporary directory in the filesystem, and also uses this directory as a place for newly uploaded files/images to live until they are finally decided to be saved into the document inside the Notes Database. The files in this temporary directory are made available to the user via a special URL.</p>
  656. <h2>How does the Persistence Service Work?</h2>
  657. <p>The XPages server has a server-wide property which determines the <strong>Base Persistence Directory.</strong></p>
  658. <p>The default value for this is <strong>&lt;DominoDataDirectory&gt;/xsppers </strong> but you can configure it to be anywhere you like using the xsp property <strong>xsp.state.persistence.directory</strong></p>
  659. <p>When a new XPages Application starts up, it is assigned an &#8216;Application Id&#8217; which is really just a sequential number, so the first application that starts up gets number 1, next number 2 etc.</p>
  660. <p>When a user starts using an Application they are given a &#8216;session id&#8217; which is a bit more like a Universal ID.</p>
  661. <blockquote><p>Side Note: I&#8217;m not sure how this technique goes with Anonymous access as I have only been programming for authenticated users.</p></blockquote>
  662. <p>The persistence service uses these 3 pieces of information to determine a user&#8217;s persistence directory for that application.</p>
  663. <p>So lets assume, my Base persistence directory is <strong>C:\Domino\Data\xsppers</strong>, and the application id is <strong>1</strong> and my session id is <strong>9847239OENTHUDO</strong> then we can find my persistence directory for that application in the file system at <strong>&lt;xsppers&gt;/&lt;applicationid&gt;/&lt;sessionid&gt;</strong> or in this example:</p>
  664. <p>C:\Domino\Data\xsppers\1\9847239OENTHUDO\</p>
  665. <p>Now here is the handy part, XPages has a resource provider which maps a special URL <strong>/xsp/.ibmmodres/persistence</strong> to the user&#8217;s persistence directory for that application.</p>
  666. <p>So lets assume we create a folder &#8216;myfolder&#8217; under my user persistence directory and put a file &#8216;myfile.pdf&#8217; in that folder::</p>
  667. <p>C:\Domino\Data\xsppers\1\9847239OENTHUDO\myfolder\myfile.pdf</p>
  668. <p>This file would then be available via the url:</p>
  669. <p>https://yourserver/yourapp.nsf<strong>/xsp/.ibmmodres/persistence/myfolder/myfile.pdf</strong></p>
  670. <p>If another user tried to use the same url it would not work because they would have a different session id, and would be mapping to a different directory</p>
  671. <h3>Cleaning up files</h3>
  672. <p>Another handy feature of the persistence service is that it cleans up after itself! So you can stress less about generating a massive zip file knowing that it won&#8217;t be left around for too long and end up filling up your server&#8217;s hard drive.</p>
  673. <p>After a user session for an application times-out then the user&#8217;s persistence directory and all its contents will be deleted.</p>
  674. <p>After the entire application times-out, then the application&#8217;s persistence directory  e.g. <strong>C:\Domino\Data\xsppers\1</strong> and all it&#8217;s contents will be deleted.</p>
  675. <h2>So how do I actually use this?</h2>
  676. <p>So your main 2 weapons here are :</p>
  677. <ul>
  678. <li>You have a User&#8217;s Persistence Directory in which you can generate files (which will also clean up after itself)</li>
  679. <li>You have a handy URL available for the user to access the User&#8217;s Persistence Directory</li>
  680. </ul>
  681. <p>To generate files into the User&#8217;s Persistence directory we can either just figure out the path of the user&#8217;s application directory and write files straight to it, or alternatively we can also ask the applications PersistenceService to do it for us.</p>
  682. <h3>Doing it ourselves</h3>
  683. <p>To do it ourselves we need to be able to figure out the User&#8217;s persistence directory.  I have included a Utility class at the bottom of this post with some handy methods. Let&#8217;s have a look at 3 of the methods:</p><pre class="urvanov-syntax-highlighter-plain-tag">public static String getSessionId() {
  684. return SessionUtil.getSessionId(FacesContext.getCurrentInstance());
  685. }
  686.  
  687. public static String getBasePersistenceFolder() {
  688.  
  689. ApplicationEx ex = ApplicationEx.getInstance();
  690.  
  691. String propDir = ex.getProperty("xsp.state.persistence.directory", null);
  692. if (StringUtil.isEmpty(propDir)) {
  693. File defaultDir = SystemUtil.DEFAULT_PERSISTENCEDIR;
  694. return defaultDir.getAbsolutePath();
  695. } else {
  696. return propDir;
  697. }
  698.  
  699. }
  700.  
  701. public static String getPersistenceSessionFolder() {
  702.  
  703. ApplicationEx ex = ApplicationEx.getInstance();
  704.  
  705. return getBasePersistenceFolder() + File.separator + ex.getApplicationId() + File.separator + getSessionId();
  706.  
  707. }</pre><p>So the main one here is <strong>getPersistenceSessionFolder</strong> and you can see it is just building up that folder location using the 3 parts mentioned earlier. We get the BasePersistenceFolder (xsppers) by querying the xsp property, and if it has not been set using an xsp property, we fall back to the default for the current platform.<br />
  708. We get the session id using the com.ibm.xsp.util.SessionUtil class</p>
  709. <p>Once you have access to this folder, you can create folders and files directly in here. The convention of the PersistenceService is to place your generated files within a subfolder of the User Persistence Directory e.g. <strong>&lt;userpersistencedir/&lt;somefolder&gt;/&lt;somefile&gt;</strong> instead of &lt;userpersistencedir&gt;/&lt;somefile&gt;. If you do not follow this convention then your download url will not work as it expects exactly this structure.</p>
  710. <h3>Doing it using the PersistenceService</h3>
  711. <p>An XPages Application has a PersistenceService which can be used for creating a &#8216;PersistedContent&#8217; object. This persisted content object can be used to get an OutputStream to write to, and also the corresponding java &#8216;File&#8217; object for that PersistedContent.</p>
  712. <p>The utility class has a method which shows how to get the persistence service. Note the persistence service is marked as Deprecated but you can still use it.</p><pre class="urvanov-syntax-highlighter-plain-tag">@SuppressWarnings("deprecation")
  713. public static PersistenceService getPersistenceService() {
  714.  
  715. ApplicationEx ex = ApplicationEx.getInstance();
  716. return ex.getPersistenceService();
  717.  
  718. }</pre><p>Also the utility class has a method to help create PersistedContent. You can see all the Persistence service methods follow the convention of using a folder and a file name.</p><pre class="urvanov-syntax-highlighter-plain-tag">public static PersistedContent createPersistedContent(String folderName, String fileName)
  719. throws IOException, NotesException {
  720.  
  721. String sessionId = getSessionId();
  722.  
  723. PersistenceService ps = getPersistenceService();
  724.  
  725. PersistedContent pd = ps.createPersistedContent(sessionId, folderName, fileName);
  726.  
  727. return pd;
  728.  
  729. }</pre><p>And you can see how this is used in the method persistInputStream</p>
  730. <blockquote><p>Note: I am using Apache Commons IOUtils for copying a stream but if you cannot use this then you can replace with another stream copying algorithm or perhaps using IBM Commons StreamUtil.copy(inputstream, outputstream). I am using Apache commons because it automatically buffers the streams so that if you are dealing with large files you don&#8217;t use too much memory</p></blockquote>
  731. <p></p><pre class="urvanov-syntax-highlighter-plain-tag">public static File persistInputStream(InputStream is, String folderName, String fileName)
  732. throws NotesException, IOException {
  733. String sessionId = getSessionId();
  734. return persistInputStream(is, sessionId, folderName, fileName);
  735. }
  736.  
  737. public static File persistInputStream(InputStream is, String sessionId, String folderName, String fileName)
  738. throws IOException {
  739.  
  740. PersistenceService ps = getPersistenceService();
  741.  
  742. PersistedContent pd = ps.createPersistedContent(sessionId, folderName, fileName);
  743.  
  744. OutputStream fos = pd.getOutputStream();
  745.  
  746. org.apache.commons.io.IOUtils.copy(is, fos);
  747.  
  748. fos.close();
  749.  
  750. return pd.getContentAsFile();
  751.  
  752. }</pre><p></p>
  753. <h2>Providing a Link to download the file</h2>
  754. <p>This is the easy part. all you have to do is use the standard link control!</p><pre class="urvanov-syntax-highlighter-plain-tag">&lt;xp:link escape="true" text="Download Me" id="link1"
  755.    value="/xsp/.ibmmodres/persistence/myfolder/myfile.pdf"&gt;&lt;/xp:link&gt;</pre><p></p>
  756. <h2>Summary</h2>
  757. <p>So I have found this way to be a very useful way to manage generating and downloading files, I hope it has been interesting / useful for you and if you have any questions or have found a mistake in this post please leave a comment and I promise to respond!</p>
  758. <h2>Utility Class</h2>
  759. <p>As promised here is the utility class which is also <a href="https://openntf.org/XSnippets.nsf/snippet.xsp?id=persistence-service-utility-class" target="_blank" rel="noopener">available as an XSnippet</a></p><pre class="urvanov-syntax-highlighter-plain-tag">package com.jord.xsp.file;
  760.  
  761. import java.io.File;
  762. import java.io.FileInputStream;
  763. import java.io.IOException;
  764. import java.io.InputStream;
  765. import java.io.OutputStream;
  766. import java.util.Arrays;
  767.  
  768. import javax.faces.context.FacesContext;
  769.  
  770. import com.ibm.commons.util.StringUtil;
  771. import com.ibm.xsp.application.ApplicationEx;
  772. import com.ibm.xsp.component.UIFileuploadEx.UploadedFile;
  773. import com.ibm.xsp.http.IUploadedFile;
  774. import com.ibm.xsp.persistence.PersistedContent;
  775. import com.ibm.xsp.persistence.PersistenceService;
  776. import com.ibm.xsp.util.SessionUtil;
  777. import com.ibm.xsp.util.SystemUtil;
  778.  
  779. import lotus.domino.NotesException;
  780.  
  781. public class PersistenceUtil {
  782.  
  783. @SuppressWarnings("deprecation")
  784. public static PersistenceService getPersistenceService() {
  785.  
  786. ApplicationEx ex = ApplicationEx.getInstance();
  787. return ex.getPersistenceService();
  788.  
  789. }
  790.  
  791. public static String getSessionId() {
  792. return SessionUtil.getSessionId(FacesContext.getCurrentInstance());
  793. }
  794.  
  795. public static String getBasePersistenceFolder() {
  796.  
  797. ApplicationEx ex = ApplicationEx.getInstance();
  798.  
  799. String propDir = ex.getProperty("xsp.state.persistence.directory", null);
  800. if (StringUtil.isEmpty(propDir)) {
  801. File defaultDir = SystemUtil.DEFAULT_PERSISTENCEDIR;
  802. return defaultDir.getAbsolutePath();
  803. } else {
  804. return propDir;
  805. }
  806.  
  807. }
  808.  
  809. public static String getPersistenceSessionFolder() {
  810.  
  811. ApplicationEx ex = ApplicationEx.getInstance();
  812.  
  813. return getBasePersistenceFolder() + File.separator + ex.getApplicationId() + File.separator + getSessionId();
  814.  
  815. }
  816.  
  817. public static File createdPath(File file) {
  818. File dir = file;
  819. if (!file.isDirectory())
  820. dir = file.getParentFile();
  821. dir.mkdirs();
  822. return file;
  823. }
  824.  
  825. public static File getAvailableFile(final File file) {
  826.  
  827. if (file.exists()) {
  828. File testFile;
  829. int counter = 0;
  830. String extension = "";
  831. String fileName = file.getAbsolutePath();
  832. int dotIndex = fileName.lastIndexOf('.');
  833. if (dotIndex &gt; 0) {
  834. extension = fileName.substring(dotIndex);
  835. fileName = fileName.substring(0, dotIndex);
  836. }
  837. while ((testFile = new File(fileName + "_" + counter + extension)).exists())
  838. counter++;
  839.  
  840. return testFile;
  841.  
  842. } else {
  843. return file;
  844. }
  845.  
  846. }
  847.  
  848. /**
  849. * Returns a safe version of a given name Currently just prevents moving
  850. * directories etc
  851. *
  852. * @return
  853. */
  854. public static String safeFileName(String fileName) {
  855. return fileName.replace("/", "").replace("\\", "");
  856. }
  857.  
  858. public static File persistFile(File file, String folderName, String fileName) throws IOException, NotesException {
  859.  
  860. String sessionId = getSessionId();
  861. return persistFile(file, sessionId, folderName, fileName);
  862.  
  863. }
  864.  
  865. public static File persistFile(File file, String sessionId, String folderName, String fileName) throws IOException {
  866.  
  867. PersistenceService ps = getPersistenceService();
  868.  
  869. PersistedContent pd = ps.createPersistedContent(sessionId, folderName, fileName);
  870.  
  871. FileInputStream fis = new FileInputStream(file);
  872. OutputStream fos = pd.getOutputStream();
  873.  
  874. org.apache.commons.io.IOUtils.copy(fis, fos);
  875.  
  876. fos.close();
  877. fis.close();
  878.  
  879. return pd.getContentAsFile();
  880.  
  881. }
  882.  
  883. public static File persistInputStream(InputStream is, String folderName, String fileName)
  884. throws NotesException, IOException {
  885. String sessionId = getSessionId();
  886. return persistInputStream(is, sessionId, folderName, fileName);
  887. }
  888.  
  889. public static File persistInputStream(InputStream is, String sessionId, String folderName, String fileName)
  890. throws IOException {
  891.  
  892. PersistenceService ps = getPersistenceService();
  893.  
  894. PersistedContent pd = ps.createPersistedContent(sessionId, folderName, fileName);
  895.  
  896. OutputStream fos = pd.getOutputStream();
  897.  
  898. org.apache.commons.io.IOUtils.copy(is, fos);
  899.  
  900. fos.close();
  901.  
  902. return pd.getContentAsFile();
  903.  
  904. }
  905.  
  906. public static PersistedContent createPersistedContent(String folderName, String fileName)
  907. throws IOException, NotesException {
  908.  
  909. String sessionId = getSessionId();
  910.  
  911. PersistenceService ps = getPersistenceService();
  912.  
  913. PersistedContent pd = ps.createPersistedContent(sessionId, folderName, fileName);
  914.  
  915. return pd;
  916.  
  917. }
  918.  
  919. public static PersistedContent[] getPersistedContents(String folderName) throws IOException {
  920.  
  921. String sessionId = getSessionId();
  922.  
  923. PersistenceService ps = getPersistenceService();
  924.  
  925. PersistedContent[] pcs = ps.getPersistedContents(sessionId, folderName);
  926.  
  927. return pcs;
  928.  
  929. }
  930.  
  931. public static PersistedContent getPersistedContent(String folderName, String fileName)
  932. throws IOException, NotesException {
  933.  
  934. String sessionId = getSessionId();
  935.  
  936. PersistenceService ps = getPersistenceService();
  937.  
  938. PersistedContent pd = ps.getPersistedContent(sessionId, folderName, fileName);
  939.  
  940. return pd;
  941.  
  942. }
  943.  
  944. public static File persistUploadedFile(UploadedFile upload, String folderName) throws IOException, NotesException {
  945.  
  946. String sessionId = getSessionId();
  947. return persistUploadedFile(upload, sessionId, folderName);
  948.  
  949. }
  950.  
  951. public static File persistUploadedFile(UploadedFile upload, String sessionId, String folderName)
  952. throws IOException {
  953.  
  954. File file = upload.getUploadedFile().getServerFile();
  955. String fileName = upload.getUploadedFile().getClientFileName();
  956.  
  957. return persistFile(file, sessionId, folderName, fileName);
  958.  
  959. }
  960.  
  961. public static File persistUploadedFile(IUploadedFile upload, String sessionId, String folderName)
  962. throws IOException {
  963.  
  964. File file = upload.getServerFile();
  965. String fileName = upload.getClientFileName();
  966.  
  967. return persistFile(file, sessionId, folderName, fileName);
  968.  
  969. }
  970.  
  971. public static void deletePersistenceFolder(String folderName) throws IOException {
  972.  
  973. String sessionId = getSessionId();
  974.  
  975. String[] folders = getPersistenceService().getFolders(sessionId);
  976.  
  977. if (!Arrays.asList(folders).contains(folderName)) {
  978. return;
  979. }
  980.  
  981. getPersistenceService().deleteFolder(sessionId, folderName);
  982.  
  983. }
  984.  
  985. }</pre><p>&nbsp;</p>
  986. <p>&nbsp;</p>
  987. ]]></content:encoded>
  988. <wfw:commentRss>https://camerongregor.com/2017/09/13/generating-and-downloading-files-using-xpages-persistence-service/feed/</wfw:commentRss>
  989. <slash:comments>8</slash:comments>
  990. </item>
  991. <item>
  992. <title>Bundle Inspector &#8211; Diagnosing XPages Plugin Resolution problems</title>
  993. <link>https://camerongregor.com/2017/08/02/bundle-inspector-diagnosing-xpages-plugin-resolution-problems/</link>
  994. <comments>https://camerongregor.com/2017/08/02/bundle-inspector-diagnosing-xpages-plugin-resolution-problems/#respond</comments>
  995. <dc:creator><![CDATA[camerongregor]]></dc:creator>
  996. <pubDate>Wed, 02 Aug 2017 12:26:56 +0000</pubDate>
  997. <category><![CDATA[Software Development]]></category>
  998. <category><![CDATA[debug]]></category>
  999. <category><![CDATA[designer]]></category>
  1000. <category><![CDATA[osgi]]></category>
  1001. <category><![CDATA[plugin]]></category>
  1002. <category><![CDATA[xpages]]></category>
  1003. <guid isPermaLink="false">http://camerongregor.com/?p=492</guid>
  1004.  
  1005. <description><![CDATA[A common task with XPages development is to installing some plugins that provide additional functionality. If you do any plugin development of your own, you end up doing this task a lot more as&#46;&#46;&#46;]]></description>
  1006. <content:encoded><![CDATA[<p>A common task with XPages development is to installing some plugins that provide additional functionality.<br />
  1007. If you do any plugin development of your own, you end up doing this task a lot more as you create new plugins, install new versions etc.</p>
  1008. <p>It is also common to encounter some problems after installing plugins to your Domino Server! Maybe your expected plugin doesn&#8217;t load up at all? maybe the wrong version is loading?</p>
  1009. <p>The standard method of diagnosing these problems is to use the OSGi console via the Domino Console (for the server) or by launching Designer with the -RPARAMS -console arguments.</p>
  1010. <p>However to provide a more user friendly way to diagnose problems, I have written a plugin for both the Domino Server and Domino Designer which provides a little more user friendly experience of diagnosing plugin problems than the standard method of using the OSGi console.</p>
  1011. <blockquote><p>Disclaimer: Throughout this I use the words Plugin and Bundle interchangeably. Technically in OSGi they are called Bundles, but Eclipse calls them plugins, so there is a weird overlap. But for the purpose of this they are the same thing.</p></blockquote>
  1012. <p>The Some common root causes are:</p>
  1013. <ul>
  1014. <li><strong>You plugin is not loading at all<br />
  1015. </strong>Maybe your Server/Domino designer is not actually configured to load plugins from where you think it is?</li>
  1016. <li><strong>Some extra versions of the plugin are being loaded from somewhere you didn&#8217;t realise<br />
  1017. </strong>Plugins can be loaded from more than one location, for example NSF Update Sites, File system, Eclipse Workspace Projects (when developing on a local Domino Server). So it is often very important to inspect exactly what plugins are loaded and where they are loading from.</li>
  1018. <li><strong>You plugin is loaded but some of it&#8217;s dependencies cannot be resolved<br />
  1019. </strong>Even when plugins do load correctly, they may have problems resolving their dependencies, and then they end up with the state &#8216;INSTALLED&#8217; but not resolved, and effectively useless.</li>
  1020. </ul>
  1021. <h2>How to Install Bundle Inspector</h2>
  1022. <p>Download the <a href="https://github.com/camac/GregorbyteXspLibrary/releases">latest release of GregorbyteXspLibrary</a>, and Install the &#8216;Bundles Inspector&#8217; feature to your Domino Server and Domino Designer.<br />
  1023. I am assuming if you have a need for the Bundle Inspector, then you must know how to install plugins to Domino Server or Designer, but if you don&#8217;t know then let me know via a comment and I will detail the process.</p>
  1024. <p>You can also install the other main feature of GregorbyteXspLibrary if you would like some other XPages controls that I have blogged about before.</p>
  1025. <h2>Using in Domino Designer</h2>
  1026. <p>After you have installed and restarted, you should see 2 new buttons in the toolbar / menu bar</p>
  1027. <p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-501" src="https://camerongregor.com/wp-content/uploads/2017/08/BundleInspector_ToolbarIcons.jpg" alt="" width="756" height="140" srcset="https://camerongregor.com/wp-content/uploads/2017/08/BundleInspector_ToolbarIcons.jpg 756w, https://camerongregor.com/wp-content/uploads/2017/08/BundleInspector_ToolbarIcons-300x56.jpg 300w" sizes="(max-width: 756px) 100vw, 756px" /></p>
  1028. <p>The Puzzle piece button will launch the Bundle Inspector. You can then filter for a particular plugin that you are having a problem with.</p>
  1029. <p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-502" src="https://camerongregor.com/wp-content/uploads/2017/08/BundleInspector_Dialog.jpg" alt="" width="647" height="493" srcset="https://camerongregor.com/wp-content/uploads/2017/08/BundleInspector_Dialog.jpg 647w, https://camerongregor.com/wp-content/uploads/2017/08/BundleInspector_Dialog-300x229.jpg 300w" sizes="(max-width: 647px) 100vw, 647px" /></p>
  1030. <p>Note that by default it is a &#8216;starts with&#8217; match,  but you can use wildcards such as * if you would like to do a &#8216;contains&#8217; match as I am here for anything with &#8216;gregor&#8217; in it.</p>
  1031. <ul>
  1032. <li>Plugins are sorted alphabetically ascending, and then descending by version, so the most recent version should be at the top.</li>
  1033. <li>Plugins that are Resolved will show the green puzzle piece icon, plugins that have not resolved will show the grey puzzle piece icon.</li>
  1034. <li>Beneath the list you can view the &#8216;location&#8217; of  the selected plugin e.g. where did the plugin load from.</li>
  1035. </ul>
  1036. <p>If you would like to see the reason why a plugin has not resolved, select it from the list and click OK, as you can see here, the libphonenumber is plugin is not loaded because there is a newer version of it loaded.</p>
  1037. <p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-503" src="https://camerongregor.com/wp-content/uploads/2017/08/BundleInspector_DialogDiagnosis.jpg" alt="" width="518" height="141" srcset="https://camerongregor.com/wp-content/uploads/2017/08/BundleInspector_DialogDiagnosis.jpg 518w, https://camerongregor.com/wp-content/uploads/2017/08/BundleInspector_DialogDiagnosis-300x82.jpg 300w" sizes="(max-width: 518px) 100vw, 518px" /></p>
  1038. <h2>Using on a Domino Server</h2>
  1039. <p>The way Bundles Inspector works on the server is, by providing a &#8216;bundlesBean&#8217; from an XspLibrary. To use this bean, you need to setup a particular page in an XPages Database.</p>
  1040. <p>For these steps, It is assumed that you have the Bundles Inspector plugin installed to both your server and your Domino Designer.</p>
  1041. <h3>Setup the XPage</h3>
  1042. <p>It is a good idea to put the BundleInspector XPage in a database that does not depend on any other Xpages libraries. This way, if there is a problem with any of the Xpages libraries that you usually use, it will not affect the BundlesInspector Xpage. You can also put access control on this database if you want to restrict who can use the Bundles Inspector.</p>
  1043. <p>Go to the XPages Properties page and enable the &#8216;com.gregorbyte.xsp.bundlesinspector&#8217; library</p>
  1044. <p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-505" src="https://camerongregor.com/wp-content/uploads/2017/08/BundleInspector_EnableLibrary.jpg" alt="" width="412" height="120" srcset="https://camerongregor.com/wp-content/uploads/2017/08/BundleInspector_EnableLibrary.jpg 412w, https://camerongregor.com/wp-content/uploads/2017/08/BundleInspector_EnableLibrary-300x87.jpg 300w" sizes="(max-width: 412px) 100vw, 412px" />Then Create a new xpage in your desired database</p>
  1045. <p>Select the &#8216;Copy Sample XPage to Clipboard&#8217; button.</p>
  1046. <p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-501" src="https://camerongregor.com/wp-content/uploads/2017/08/BundleInspector_ToolbarIcons.jpg" alt="" width="756" height="140" srcset="https://camerongregor.com/wp-content/uploads/2017/08/BundleInspector_ToolbarIcons.jpg 756w, https://camerongregor.com/wp-content/uploads/2017/08/BundleInspector_ToolbarIcons-300x56.jpg 300w" sizes="(max-width: 756px) 100vw, 756px" /></p>
  1047. <p>You should then see the confirmation message</p>
  1048. <p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-506" src="https://camerongregor.com/wp-content/uploads/2017/08/BundleInspector_SampleCopied.jpg" alt="" width="518" height="141" srcset="https://camerongregor.com/wp-content/uploads/2017/08/BundleInspector_SampleCopied.jpg 518w, https://camerongregor.com/wp-content/uploads/2017/08/BundleInspector_SampleCopied-300x82.jpg 300w" sizes="(max-width: 518px) 100vw, 518px" /></p>
  1049. <p>Go to the Source tab of your XPage and paste the contents over the top</p>
  1050. <p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-507" src="https://camerongregor.com/wp-content/uploads/2017/08/BundleInspector_Pasted.jpg" alt="" width="597" height="373" srcset="https://camerongregor.com/wp-content/uploads/2017/08/BundleInspector_Pasted.jpg 597w, https://camerongregor.com/wp-content/uploads/2017/08/BundleInspector_Pasted-300x187.jpg 300w" sizes="(max-width: 597px) 100vw, 597px" /></p>
  1051. <p>Feel free to customise this page to however you like, you could change the colours layout etc. The page is merely provided as a starting point.</p>
  1052. <h3>Using the Bundle Inspector for the Server</h3>
  1053. <p>Navigate to your XPage in your web browser. (The XPage should be on the Server that you are diagnosing bundles for)</p>
  1054. <ul>
  1055. <li>You can filter for the plugin your are interested in using the filter</li>
  1056. <li>There is also an example of including a quick link to a common search e.g. extlib</li>
  1057. <li>The bundle location is shown in the table so you can see where the plugin is loading from</li>
  1058. <li>The bundle state is shown on the Right, green means resolved, red means not resolved</li>
  1059. </ul>
  1060. <p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-508" src="https://camerongregor.com/wp-content/uploads/2017/08/BundleInspector_Web.jpg" alt="" width="1171" height="275" srcset="https://camerongregor.com/wp-content/uploads/2017/08/BundleInspector_Web.jpg 1171w, https://camerongregor.com/wp-content/uploads/2017/08/BundleInspector_Web-300x70.jpg 300w, https://camerongregor.com/wp-content/uploads/2017/08/BundleInspector_Web-768x180.jpg 768w, https://camerongregor.com/wp-content/uploads/2017/08/BundleInspector_Web-1024x240.jpg 1024w" sizes="(max-width: 1171px) 100vw, 1171px" /></p>
  1061. <p>If a plugin is not resolved you can click the &#8216;Diagnose&#8217; button and the diagnosis will show beneath the row of the plugin</p>
  1062. <p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-509" src="https://camerongregor.com/wp-content/uploads/2017/08/BundleInspector_WebDiagnose.jpg" alt="" width="1237" height="99" srcset="https://camerongregor.com/wp-content/uploads/2017/08/BundleInspector_WebDiagnose.jpg 1237w, https://camerongregor.com/wp-content/uploads/2017/08/BundleInspector_WebDiagnose-300x24.jpg 300w, https://camerongregor.com/wp-content/uploads/2017/08/BundleInspector_WebDiagnose-768x61.jpg 768w, https://camerongregor.com/wp-content/uploads/2017/08/BundleInspector_WebDiagnose-1024x82.jpg 1024w" sizes="(max-width: 1237px) 100vw, 1237px" /></p>
  1063. <p>&nbsp;</p>
  1064. <h2>Conclusion</h2>
  1065. <p>So there you have it, the aim of the Bundle Inspector is to give a better experience for diagnosing plugin problems.</p>
  1066. <p>Let me know what you think, would this help you? any improvements? Can you think of a better name the Bundle Inspector? I don&#8217;t really like it but that is what I went with&#8230;</p>
  1067. <p>If you have any problems installing let me know!</p>
  1068. ]]></content:encoded>
  1069. <wfw:commentRss>https://camerongregor.com/2017/08/02/bundle-inspector-diagnosing-xpages-plugin-resolution-problems/feed/</wfw:commentRss>
  1070. <slash:comments>0</slash:comments>
  1071. </item>
  1072. <item>
  1073. <title>Swiper Official Version 2 Release</title>
  1074. <link>https://camerongregor.com/2017/07/21/swiper-official-version-2-release/</link>
  1075. <comments>https://camerongregor.com/2017/07/21/swiper-official-version-2-release/#comments</comments>
  1076. <dc:creator><![CDATA[camerongregor]]></dc:creator>
  1077. <pubDate>Fri, 21 Jul 2017 13:52:52 +0000</pubDate>
  1078. <category><![CDATA[Software Development]]></category>
  1079. <category><![CDATA[designer]]></category>
  1080. <category><![CDATA[sourcecontrol]]></category>
  1081. <category><![CDATA[swiper]]></category>
  1082. <guid isPermaLink="false">http://camerongregor.com/?p=495</guid>
  1083.  
  1084. <description><![CDATA[So I have finally posted Swiper version 2 to OpenNTF! (and Github of course) Swiper OpenNTF Project Page Swiper Github Releases The latest version is 2.0.1,  and is the same as 2.0.0beta but with&#46;&#46;&#46;]]></description>
  1085. <content:encoded><![CDATA[<p>So I have finally posted Swiper version 2 to OpenNTF! (and Github of course)</p>
  1086. <ul>
  1087. <li><a href="https://www.openntf.org/main.nsf/project.xsp?r=project/Swiper/summary" target="_blank" rel="noopener">Swiper OpenNTF Project Page</a></li>
  1088. <li><a href="https://github.com/camac/Swiper/releases" target="_blank" rel="noopener">Swiper Github Releases</a></li>
  1089. </ul>
  1090. <p>The latest version is 2.0.1,  and is the same as 2.0.0beta but with a bug fix for the toolbar buttons.</p>
  1091. <p>Swiper 2.0.0 beta has been available on the Github project site for a few months, and the core functionality of it works as planned. There was however a bug within the shortcut buttons that I added to the menu bar, and this is a bit of a nasty bug that can cause deletion of design elements.</p>
  1092. <p>Why has it taken me so long to fix this bug and prepare the final release? to be perfectly honest with you, I just couldn&#8217;t muster up the motivation to get this done. My development process for Swiper involves launching Domino Designer from my eclipse IDE.  After I upgraded my IBM Notes to 9.0.1 FP8 my designer launch settings no longer worked. By this time frankly my tank was empty. I knew re-configuring the launch settings was destined to be a fiddly encounter, and entire nights can be wasted with no fruitful results. All my extra-curricular projects are done in my spare-time, and this usually means between 8:30pm-11:30pm, and after a full day of problem-solving at work, I just couldn&#8217;t muster up the courage to face a task that had a high-probability of failure. I was satisfied that the beta version was available, and the project would have to wait until I felt the motivation again.</p>
  1093. <p>Well this week, I noticed a tweet from Per Henrik Lausten about &#8216;Open Source Friday&#8217;.</p>
  1094. <blockquote class="twitter-tweet" data-lang="en">
  1095. <p dir="ltr" lang="en">Contribute to open source on <a href="https://twitter.com/hashtag/OpenSourceFriday?src=hash">#OpenSourceFriday</a> (by <a href="https://twitter.com/github">@github</a>):<a href="https://t.co/EeHpi9UphG">https://t.co/EeHpi9UphG</a></p>
  1096. <p>— Per Henrik Lausten (@perlausten) <a href="https://twitter.com/perlausten/status/887551759731871744">July 19, 2017</a></p></blockquote>
  1097. <p><script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script></p>
  1098. <p>It made me think a little about Swiper, and I thought I would give it another go. I also felt validated for taking my little break from the project when I read the &#8216;<a href="https://opensource.guide/best-practices/#its-okay-to-hit-pause" target="_blank" rel="noopener">It&#8217;s ok to hit pause</a>&#8216; section of the Open Source best practices for Maintainers <img src="https://s.w.org/images/core/emoji/15.0.3/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
  1099. <p>So I needed to fix my Designer Launch settings, I really don&#8217;t know how to do this by myself and have relied on others for this information. Previously it was Mikkel Heisterberg, and then for the past few years I turned to <a href="https://twitter.com/RalfMPetter" target="_blank" rel="noopener">Ralf M Petter</a>. I was very sad to see recent news that Ralf passed away from a cancer related illness. I would just like to express my sympathy to Ralf&#8217;s family for their loss. Ralf&#8217;s blog posts on setting up Eclipse to launch Notes and Designer were extremely helpful to me whilst developing Swiper and other plugins that I have made for Notes and Designer. Ralf was always quick to respond to my questions and his expertise will be missed.</p>
  1100. <p>A couple of months ago I had asked Ralf if he knew how to launch Designer FP8 from eclipse and surely enough within a day he had provided me with updated instructions, I had also asked Gary Marjoram @ IBM for some advice and armed with all this advice I was able to get FP8 launching again after a couple of hours tweaking.</p>
  1101. <p>Tonight I was able to debug the toolbar buttons again and therefore I was able to release the official version 2.</p>
  1102. <p>Let me know if you have any problems or suggestions!</p>
  1103. ]]></content:encoded>
  1104. <wfw:commentRss>https://camerongregor.com/2017/07/21/swiper-official-version-2-release/feed/</wfw:commentRss>
  1105. <slash:comments>5</slash:comments>
  1106. </item>
  1107. <item>
  1108. <title>Auto-width Bootstrap Column XPages Controls</title>
  1109. <link>https://camerongregor.com/2017/03/22/auto-width-bootstrap-column-xpages-controls/</link>
  1110. <comments>https://camerongregor.com/2017/03/22/auto-width-bootstrap-column-xpages-controls/#respond</comments>
  1111. <dc:creator><![CDATA[camerongregor]]></dc:creator>
  1112. <pubDate>Tue, 21 Mar 2017 13:13:18 +0000</pubDate>
  1113. <category><![CDATA[Software Development]]></category>
  1114. <category><![CDATA[bootstrap]]></category>
  1115. <category><![CDATA[control]]></category>
  1116. <category><![CDATA[xpages]]></category>
  1117. <guid isPermaLink="false">http://camerongregor.com/?p=479</guid>
  1118.  
  1119. <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>
  1120. <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 />
  1121. Finally, I have moved on to my next project and I am now using bootstrap (version 3)</p>
  1122. <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>
  1123. <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>
  1124. <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>
  1125. <h2>Example XPages Markup</h2>
  1126. <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>
  1127. <p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-481" src="https://camerongregor.com/wp-content/uploads/2017/03/BootstrapDemoPage.jpg" alt="" width="981" height="140" srcset="https://camerongregor.com/wp-content/uploads/2017/03/BootstrapDemoPage.jpg 981w, https://camerongregor.com/wp-content/uploads/2017/03/BootstrapDemoPage-300x43.jpg 300w, https://camerongregor.com/wp-content/uploads/2017/03/BootstrapDemoPage-768x110.jpg 768w" sizes="(max-width: 981px) 100vw, 981px" /></p>
  1128. <p>Here is a simple example of 2-column layout with equal width columns</p><pre class="urvanov-syntax-highlighter-plain-tag">&lt;gb:row id="row1"&gt;
  1129.    &lt;gb:column id="column1"&gt;These two columns&lt;/gb:column&gt;
  1130.    &lt;gb:column id="column2"&gt;are Equal Width&lt;/gb:column&gt;
  1131. &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>
  1132. <p>If there have 3 columns, then they would each automatically be 4 columns wide (12 divided by 3 columns).</p><pre class="urvanov-syntax-highlighter-plain-tag">&lt;gb:row id="row5"&gt;
  1133.    &lt;gb:column id="column3"&gt;These three &lt;/gb:column&gt;
  1134.    &lt;gb:column id="column10"&gt;columns are also&lt;/gb:column&gt;
  1135.    &lt;gb:column id="column11"&gt;Equal Width&lt;/gb:column&gt;
  1136. &lt;/gb:row&gt;</pre><p></p>
  1137. <h3>Specifying a column width</h3>
  1138. <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="urvanov-syntax-highlighter-plain-tag">&lt;gb:row id="row2"&gt;
  1139.    &lt;gb:column id="column4"&gt;These columns have&lt;/gb:column&gt;
  1140.    &lt;gb:column id="column5" span="6"&gt;the middle with span="6"&lt;/gb:column&gt;
  1141.    &lt;gb:column id="column6"&gt;and the outer columns are auto width&lt;/gb:column&gt;
  1142. &lt;/gb:row&gt;</pre><p></p>
  1143. <h3>Specifying alignment</h3>
  1144. <p>I&#8217;ve also added an align property so I can quickly align the contents.</p><pre class="urvanov-syntax-highlighter-plain-tag">&lt;gb:row id="row4"&gt;
  1145.    &lt;gb:column id="column7" align="left"&gt;This is align left (default)&lt;/gb:column&gt;
  1146.    &lt;gb:column id="column8" align="center"&gt;This is align middle&lt;/gb:column&gt;
  1147.    &lt;gb:column id="column9" align="right"&gt;This is align left&lt;/gb:column&gt;
  1148. &lt;/gb:row&gt;</pre><p></p>
  1149. <h3>Full Row column</h3>
  1150. <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="urvanov-syntax-highlighter-plain-tag">&lt;gb:row id="row3"&gt;
  1151. If you want a Full Row, then just don't include columns
  1152. &lt;/gb:row&gt;</pre><p></p>
  1153. <h3>Using style and styleClass</h3>
  1154. <p>You can still use style and styleClass properties as you normally would.</p>
  1155. <h3>Container Control</h3>
  1156. <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="urvanov-syntax-highlighter-plain-tag">&lt;gb:container id="container1" fluid="true"&gt;
  1157.  
  1158.    &lt;!-- rows, columns, your content --&gt;
  1159.  
  1160. &lt;/gb:container&gt;</pre><p></p>
  1161. <h2>Overview of creating the controls</h2>
  1162. <p>Here is what we are going to do:</p>
  1163. <ol>
  1164. <li>Create the Container UIComponent</li>
  1165. <li>Create the Column UIComponent</li>
  1166. <li>Create the Row UIComponent</li>
  1167. <li>Provide Control Definitions via an xsp-config file so that designer knows how to use them</li>
  1168. <li>Program in the auto-width algorithm</li>
  1169. </ol>
  1170. <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>
  1171. <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>
  1172. <h3>Creating the Container Component</h3>
  1173. <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>
  1174. <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="urvanov-syntax-highlighter-plain-tag">package com.gregorbyte.xsp.component.bootstrap;
  1175.  
  1176. import javax.faces.context.FacesContext;
  1177. import javax.faces.el.ValueBinding;
  1178.  
  1179. import com.gregorbyte.xsp.util.GregorbyteUtil;
  1180. import com.ibm.xsp.component.UIPanelEx;
  1181.  
  1182. public class UIContainer extends UIPanelEx {
  1183.  
  1184. public Boolean fluid;
  1185.  
  1186. public UIContainer() {
  1187.  
  1188. }
  1189.  
  1190. @Override
  1191. public String getStyleClass() {
  1192.  
  1193. String parent = super.getStyleClass();
  1194.  
  1195. if (isFluid()) {
  1196. return GregorbyteUtil.concatStyleClasses("container-fluid", parent);
  1197. } else {
  1198. return GregorbyteUtil.concatStyleClasses("container", parent);
  1199. }
  1200.  
  1201. }
  1202.  
  1203. public Boolean isFluid() {
  1204.  
  1205. if (this.fluid != null) {
  1206. return this.fluid;
  1207. }
  1208.  
  1209. ValueBinding vb = getValueBinding("fluid");
  1210.  
  1211. if (vb != null) {
  1212. return (Boolean) vb.getValue(getFacesContext());
  1213. }
  1214.  
  1215. return false;
  1216.  
  1217. }
  1218.  
  1219. public void setFluid(Boolean fluid) {
  1220. this.fluid = fluid;
  1221. }
  1222.  
  1223. @Override
  1224. public void restoreState(FacesContext context, Object state) {
  1225.  
  1226. Object[] values = (Object[]) state;
  1227.  
  1228. super.restoreState(context, values[0]);
  1229. fluid = (Boolean) values[1];
  1230.  
  1231. }
  1232.  
  1233. @Override
  1234. public Object saveState(FacesContext context) {
  1235.  
  1236. Object[] values = new Object[2];
  1237.  
  1238. values[0] = super.saveState(context);
  1239. values[1] = fluid;
  1240.  
  1241. return values;
  1242.  
  1243. }
  1244.  
  1245. }</pre><p></p>
  1246. <h3>Creating the Column Component</h3>
  1247. <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>
  1248. <ul>
  1249. <li><strong>span</strong> &#8211; This will be an integer that can be set to specify the column span of the bootstrap grid</li>
  1250. <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>
  1251. </ul>
  1252. <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>
  1253. <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>
  1254. <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>
  1255. <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>
  1256. <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="urvanov-syntax-highlighter-plain-tag">package com.gregorbyte.xsp.component.bootstrap;
  1257.  
  1258. import javax.faces.context.FacesContext;
  1259. import javax.faces.el.ValueBinding;
  1260.  
  1261. import com.gregorbyte.xsp.util.GregorbyteUtil;
  1262. import com.ibm.commons.util.StringUtil;
  1263. import com.ibm.xsp.component.UIPanelEx;
  1264.  
  1265. public class UIColumn extends UIPanelEx {
  1266.  
  1267. private Integer guessSpan = null;
  1268.  
  1269. // Defaults for Medium Columns, use styleClass for specific layouts
  1270. private Integer span = null;
  1271.  
  1272. private String align = null;
  1273.  
  1274. public UIColumn() {
  1275. super();
  1276. }
  1277.  
  1278. private Integer getEffectiveSpan() {
  1279. if (getSpan() != null) {
  1280. return getSpan();
  1281. } else if (getGuessSpan() != null) {
  1282. return getGuessSpan();
  1283. }
  1284.  
  1285. return 12;
  1286. }
  1287.  
  1288. @Override
  1289. public String getStyleClass() {
  1290.  
  1291. String styleClass = super.getStyleClass();
  1292.  
  1293. styleClass = GregorbyteUtil.concatStyleClasses(styleClass, "col-md-" + getEffectiveSpan());
  1294.  
  1295. String align = getAlign();
  1296.  
  1297. if (StringUtil.equals(align, "left")) {
  1298. styleClass = GregorbyteUtil.concatStyleClasses(styleClass, "text-left");
  1299. } else if (StringUtil.equals(align, "right")) {
  1300. styleClass = GregorbyteUtil.concatStyleClasses(styleClass, "text-right");
  1301. } else if (StringUtil.equals(align, "center")) {
  1302. styleClass = GregorbyteUtil.concatStyleClasses(styleClass, "text-center");
  1303. }
  1304.  
  1305. return styleClass;
  1306.  
  1307. }
  1308.  
  1309. public Integer getGuessSpan() {
  1310. return guessSpan;
  1311. }
  1312.  
  1313. public void setGuessSpan(Integer guessSpan) {
  1314. this.guessSpan = guessSpan;
  1315. }
  1316.  
  1317. public Integer getSpan() {
  1318.  
  1319. if (this.span != null) {
  1320. return this.span;
  1321. }
  1322.  
  1323. ValueBinding vb = getValueBinding("span");
  1324.  
  1325. if (vb != null) {
  1326. return (Integer) vb.getValue(getFacesContext());
  1327. }
  1328.  
  1329. return null;
  1330.  
  1331. }
  1332.  
  1333. public void setSpan(Integer span) {
  1334. this.span = span;
  1335. }
  1336.  
  1337. public String getAlign() {
  1338.  
  1339. if (this.align != null) {
  1340. return this.align;
  1341. }
  1342.  
  1343. ValueBinding vb = getValueBinding("align");
  1344.  
  1345. if (vb != null) {
  1346. return (String) vb.getValue(getFacesContext());
  1347. }
  1348.  
  1349. return null;
  1350.  
  1351. }
  1352.  
  1353. public void setAlign(String align) {
  1354. this.align = align;
  1355. }
  1356.  
  1357. @Override
  1358. public void restoreState(FacesContext context, Object state) {
  1359.  
  1360. Object[] values = (Object[]) state;
  1361.  
  1362. super.restoreState(context, values[0]);
  1363.  
  1364. guessSpan = (Integer) values[1];
  1365. span = (Integer) values[2];
  1366. align = (String) values[3];
  1367.  
  1368. }
  1369.  
  1370. @Override
  1371. public Object saveState(FacesContext context) {
  1372.  
  1373. Object[] values = new Object[4];
  1374.  
  1375. values[0] = super.saveState(context);
  1376. values[1] = guessSpan;
  1377. values[2] = span;
  1378. values[3] = align;
  1379.  
  1380. return values;
  1381.  
  1382. }
  1383.  
  1384. }</pre><p></p>
  1385. <h3>Creating the Row Component</h3>
  1386. <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>
  1387. <h3>Programming the auto-width algorithm</h3>
  1388. <p>The auto-width algorithm does the following</p>
  1389. <ol>
  1390. <li>Finds the total span of columns that have been explicitly allocated using the <strong>span</strong> property</li>
  1391. <li>Figures out the remaining span available (e.g. 12 &#8211; allocated)</li>
  1392. <li>Figures out the average span to allocate to remaining columns without a span</li>
  1393. <li>Allocates the &#8216;guessed&#8217; span to the remaining columns</li>
  1394. </ol>
  1395. <p></p><pre class="urvanov-syntax-highlighter-plain-tag">package com.gregorbyte.xsp.component.bootstrap;
  1396.  
  1397. import java.io.IOException;
  1398. import java.util.ArrayList;
  1399. import java.util.List;
  1400.  
  1401. import javax.faces.FacesException;
  1402. import javax.faces.component.UIComponent;
  1403. import javax.faces.context.FacesContext;
  1404.  
  1405. import com.gregorbyte.xsp.util.GregorbyteUtil;
  1406. import com.ibm.xsp.component.UIPanelEx;
  1407. import com.ibm.xsp.util.TypedUtil;
  1408.  
  1409. public class UIRow extends UIPanelEx {
  1410.  
  1411. private Boolean autoColumn = false;
  1412.  
  1413. public UIRow() {
  1414. super();
  1415.  
  1416. }
  1417.  
  1418. @Override
  1419. public String getStyleClass() {
  1420.  
  1421. String parent = super.getStyleClass();
  1422.  
  1423. return GregorbyteUtil.concatStyleClasses("row", parent);
  1424.  
  1425. }
  1426.  
  1427. @Override
  1428. public void encodeBegin(FacesContext context) throws IOException {
  1429.  
  1430. super.encodeBegin(context);
  1431.  
  1432. calculateColumnSpans(context);
  1433. if (autoColumn) {
  1434. context.getResponseWriter().startElement("div", null);
  1435. context.getResponseWriter().writeAttribute("class", "col-md-12", null);
  1436. }
  1437.  
  1438. }
  1439.  
  1440. @Override
  1441. public void encodeEnd(FacesContext context) throws IOException {
  1442.  
  1443. if (autoColumn) {
  1444. context.getResponseWriter().endElement("div");
  1445. }
  1446.  
  1447. super.encodeEnd(context);
  1448. }
  1449.  
  1450. public void calculateColumnSpans(FacesContext context) throws FacesException {
  1451.  
  1452. List&lt;UIComponent&gt; kids = TypedUtil.getChildren(this);
  1453.  
  1454. List&lt;UIColumn&gt; cols = new ArrayList&lt;UIColumn&gt;();
  1455.  
  1456. int totalMediumSpecified = 0;
  1457.  
  1458. for (UIComponent comp : kids) {
  1459. if (comp instanceof UIColumn) {
  1460.  
  1461. UIColumn col = (UIColumn) comp;
  1462.  
  1463. if (col.getSpan() != null) {
  1464. totalMediumSpecified += col.getSpan();
  1465. } else {
  1466. cols.add(col);
  1467. }
  1468.  
  1469. }
  1470. }
  1471.  
  1472. // If No Columns we will render
  1473. autoColumn = (cols.size() == 0);
  1474.  
  1475. if (autoColumn)
  1476. return;
  1477.  
  1478. int remaining = 12 - totalMediumSpecified;
  1479.  
  1480. Integer colGuess = remaining / cols.size();
  1481.  
  1482. if (colGuess != null) {
  1483. for (UIColumn uiColumn : cols) {
  1484. uiColumn.setGuessSpan(colGuess);
  1485. }
  1486. }
  1487.  
  1488. }
  1489.  
  1490. @Override
  1491. public void restoreState(FacesContext context, Object state) {
  1492.  
  1493. Object[] values = (Object[]) state;
  1494.  
  1495. super.restoreState(context, values[0]);
  1496. autoColumn = (Boolean) values[1];
  1497.  
  1498. }
  1499.  
  1500. @Override
  1501. public Object saveState(FacesContext context) {
  1502.  
  1503. Object[] values = new Object[2];
  1504.  
  1505. values[0] = super.saveState(context);
  1506. values[1] = autoColumn;
  1507.  
  1508. return values;
  1509.  
  1510. }
  1511.  
  1512. }</pre><p></p>
  1513. <h2>Provide Component Definition via xsp-config file</h2>
  1514. <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>
  1515. <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>
  1516. <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>
  1517. <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="urvanov-syntax-highlighter-plain-tag">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
  1518. &lt;faces-config&gt;
  1519.  
  1520. &lt;faces-config-extension&gt;
  1521. &lt;namespace-uri&gt;http://www.gregorbyte.com/xsp/&lt;/namespace-uri&gt;
  1522. &lt;default-prefix&gt;gb&lt;/default-prefix&gt;
  1523. &lt;/faces-config-extension&gt;
  1524.  
  1525. &lt;component&gt;
  1526.  
  1527. &lt;description&gt;Row Container for Bootstrap Columns
  1528. &lt;/description&gt;
  1529. &lt;display-name&gt;Bootstrap Row&lt;/display-name&gt;
  1530. &lt;component-type&gt;com.gregorbyte.xsp.bootstrap.Row
  1531. &lt;/component-type&gt;
  1532. &lt;component-class&gt;com.gregorbyte.xsp.component.bootstrap.UIRow
  1533. &lt;/component-class&gt;
  1534.  
  1535. &lt;component-extension&gt;
  1536.  
  1537. &lt;base-component-type&gt;com.ibm.xsp.Panel
  1538. &lt;/base-component-type&gt;
  1539.  
  1540. &lt;component-family&gt;javax.faces.Panel
  1541. &lt;/component-family&gt;
  1542.  
  1543. &lt;tag-name&gt;row&lt;/tag-name&gt;
  1544.  
  1545. &lt;designer-extension&gt;
  1546. &lt;in-palette&gt;true&lt;/in-palette&gt;
  1547. &lt;category&gt;Gregorbyte Library&lt;/category&gt;
  1548. &lt;/designer-extension&gt;
  1549.  
  1550. &lt;/component-extension&gt;
  1551.  
  1552. &lt;/component&gt;
  1553.  
  1554. &lt;component&gt;
  1555.  
  1556. &lt;description&gt;Bootstrap Columns
  1557. &lt;/description&gt;
  1558. &lt;display-name&gt;Bootstrap Column&lt;/display-name&gt;
  1559. &lt;component-type&gt;com.gregorbyte.xsp.bootstrap.Column
  1560. &lt;/component-type&gt;
  1561. &lt;component-class&gt;com.gregorbyte.xsp.component.bootstrap.UIColumn
  1562. &lt;/component-class&gt;
  1563.  
  1564. &lt;property&gt;
  1565. &lt;description&gt;Adds an Alignment class to the column &lt;/description&gt;
  1566. &lt;display-name&gt;Alignment&lt;/display-name&gt;
  1567. &lt;property-name&gt;align&lt;/property-name&gt;
  1568. &lt;property-class&gt;java.lang.String&lt;/property-class&gt;
  1569. &lt;property-extension&gt;
  1570. &lt;designer-extension&gt;
  1571. &lt;editor&gt;com.ibm.workplace.designer.property.editors.comboParameterEditor&lt;/editor&gt;
  1572. &lt;editor-parameter&gt;
  1573. left
  1574. center
  1575. right
  1576. &lt;/editor-parameter&gt;
  1577. &lt;category&gt;basics&lt;/category&gt;
  1578. &lt;/designer-extension&gt;
  1579. &lt;/property-extension&gt;
  1580. &lt;/property&gt;
  1581.  
  1582. &lt;property&gt;
  1583. &lt;description&gt;Number of Columns to Span (Medium Screen size). Leave blank for auto calculation&lt;/description&gt;
  1584. &lt;display-name&gt;Columns to Span&lt;/display-name&gt;
  1585. &lt;property-name&gt;span&lt;/property-name&gt;
  1586. &lt;property-class&gt;java.lang.Integer&lt;/property-class&gt;
  1587. &lt;property-extension&gt;
  1588. &lt;allow-run-time-binding&gt;true&lt;/allow-run-time-binding&gt;
  1589. &lt;designer-extension&gt;
  1590. &lt;category&gt;basics&lt;/category&gt;
  1591. &lt;/designer-extension&gt;
  1592. &lt;/property-extension&gt;
  1593. &lt;/property&gt;
  1594.  
  1595. &lt;component-extension&gt;
  1596.  
  1597. &lt;base-component-type&gt;com.ibm.xsp.Panel
  1598. &lt;/base-component-type&gt;
  1599.  
  1600. &lt;component-family&gt;javax.faces.Panel
  1601. &lt;/component-family&gt;
  1602.  
  1603. &lt;tag-name&gt;column&lt;/tag-name&gt;
  1604.  
  1605. &lt;designer-extension&gt;
  1606. &lt;in-palette&gt;true&lt;/in-palette&gt;
  1607. &lt;category&gt;Gregorbyte Library&lt;/category&gt;
  1608. &lt;/designer-extension&gt;
  1609.  
  1610. &lt;/component-extension&gt;
  1611.  
  1612. &lt;/component&gt;
  1613.  
  1614. &lt;component&gt;
  1615.  
  1616. &lt;description&gt;Container for Bootstrap Layout
  1617. &lt;/description&gt;
  1618. &lt;display-name&gt;Bootstrap Container&lt;/display-name&gt;
  1619. &lt;component-type&gt;com.gregorbyte.xsp.bootstrap.Container
  1620. &lt;/component-type&gt;
  1621. &lt;component-class&gt;com.gregorbyte.xsp.component.bootstrap.UIContainer
  1622. &lt;/component-class&gt;
  1623.  
  1624. &lt;property&gt;
  1625. &lt;description&gt;Use Fluid Layout
  1626. &lt;/description&gt;
  1627. &lt;display-name&gt;Use Fluid Layout&lt;/display-name&gt;
  1628. &lt;property-name&gt;fluid&lt;/property-name&gt;
  1629. &lt;property-class&gt;java.lang.Boolean&lt;/property-class&gt;
  1630. &lt;property-extension&gt;
  1631. &lt;allow-run-time-binding&gt;true&lt;/allow-run-time-binding&gt;
  1632. &lt;designer-extension&gt;
  1633. &lt;category&gt;display&lt;/category&gt;
  1634. &lt;/designer-extension&gt;
  1635. &lt;/property-extension&gt;
  1636. &lt;/property&gt;
  1637.  
  1638. &lt;component-extension&gt;
  1639.  
  1640. &lt;base-component-type&gt;com.ibm.xsp.Panel
  1641. &lt;/base-component-type&gt;
  1642.  
  1643. &lt;component-family&gt;javax.faces.Panel
  1644. &lt;/component-family&gt;
  1645.  
  1646. &lt;tag-name&gt;container&lt;/tag-name&gt;
  1647.  
  1648. &lt;designer-extension&gt;
  1649. &lt;in-palette&gt;true&lt;/in-palette&gt;
  1650. &lt;category&gt;Gregorbyte Library&lt;/category&gt;
  1651. &lt;/designer-extension&gt;
  1652.  
  1653. &lt;/component-extension&gt;
  1654.  
  1655. &lt;/component&gt;
  1656.  
  1657. &lt;/faces-config&gt;</pre><p></p>
  1658. <h2>Installing</h2>
  1659. <p>If you would like to use these controls, you have 2 choices!</p>
  1660. <ol>
  1661. <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>
  1662. <li>Scavenge the necessary files from my Github repository and include it your own NSF</li>
  1663. </ol>
  1664. <p>To Scavenge the necessary files from my <a href="https://github.com/camac/GregorbyteXspLibrary" target="_blank">Github Repo</a>:</p>
  1665. <ul>
  1666. <li>You will need to place these java files in your java design elements:
  1667. <ul>
  1668. <li>/com.gregorbyte.xsp.controls/src/com/gregorbyte/xsp/component/bootstrap/UIColumn.java</li>
  1669. <li>/com.gregorbyte.xsp.controls/src/com/gregorbyte/xsp/component/bootstrap/UIRow.java</li>
  1670. <li>/com.gregorbyte.xsp.controls/src/com/gregorbyte/xsp/component/bootstrap/UIContainer.java</li>
  1671. <li>/com.gregorbyte.xsp.core/src/com/gregorbyte/xsp/util/GregorbyteUtil.java</li>
  1672. </ul>
  1673. </li>
  1674. <li>You will need to place this xsp-config file in the WEB-INF directory
  1675. <ul>
  1676. <li>/com.gregorbyte.xsp.controls/src/com/gregorbyte/xsp/config/gregorbyte-bootstrap.xsp-config</li>
  1677. </ul>
  1678. </li>
  1679. </ul>
  1680. <h1>Conclusion</h1>
  1681. <p>You can easily get the job done without these controls, but so far I have really enjoyed having them in my palette.</p>
  1682. <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>
  1683. <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>
  1684. <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>
  1685. <p>&nbsp;</p>
  1686. <h3></h3>
  1687. ]]></content:encoded>
  1688. <wfw:commentRss>https://camerongregor.com/2017/03/22/auto-width-bootstrap-column-xpages-controls/feed/</wfw:commentRss>
  1689. <slash:comments>0</slash:comments>
  1690. </item>
  1691. </channel>
  1692. </rss>
  1693.  

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