Wednesday 15 June 2011

Striving for Nirvana - Write Once, Work Everywhere with XPages

Remember when Notes 5 was launched and we thought our Notes client applications would all now magically work on the Web ? XPages with 8.5.2 is significantly closer to this goal of one code stream working in the Notes client, most common browsers, and the iPad.

An XPages application that I recently delivered was built primarily to run in the Notes client, but also from browsers for staff who were out of the office. An additional 'nice-to-have' was for the workflow approvals to be able to be completed by the General Managers via their iPads.

The application is a little slow to load, the first time, from the Notes 8.5.2 client. But by no means unusable. However, reports from those with the 8.5.3 beta are that this has been significantly improved in the next release.

For the iPad/iPhone, I refer you to this wiki article by Mark Hughes, the XPages iPhone Application Tutorial

All I had to do to render the application for the iPad was to add the following two meta tags to a custom control that had already been added to every XPage for the menu components:

<meta name="viewport" content="width=320; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;" />
<meta name="apple-mobile-web-app-capable" content="yes" />

Workflow approvals can now be executed from the General Manager iPads from wherever they choose to work. Such a neat way to demonstrate the power of XPages to management.

Tuesday 14 June 2011

A fix for the XPages Download control

One of the great features of XPages is that we can now abstract the code from the data. So we can have the documents that contain attachments in one database and the XPage that contains the code for the Download control in another.

Acknowledgements to Raz Hoe for the lead on this solution. Also to John Mackey for this post. (Important that you read John's article.) I have combined these solutions for those who need their code to work for both the Notes client and the browser, as it took me quite a while to resolve this issue:

This code needs to go in the All Properties section of each Download control:


In my 8.5.2 FP2 production applications I compute the following SSJS for the fileNameHrefValue property:

if (@IsNewDoc()) return "#"; //do nothing if document1 has not yet been saved

if (@ClientType()=="Notes") return "/.ibmmodres/domino/OpenAttachment/"+database.getServer() + "!!"+ sessionScope.mydbName + "/"+document1.getDocument().getUniversalID() + "/attachment/"+rowData.getName();

var serv = session.getHttpURL().split("?")[0];
return serv + "/" + sessionScope.mydbName+ "/0/" + document1.getDocument().getUniversalID() + "$FILE/"+ rowData.getName();

Hope you find this to be useful.

Monday 13 June 2011

SSJS creation of PDF with iText

A customer needed their new XPages application to email invoices to their clients as PDF attachments. After a bit of research, I worked out a simple way to do this using Server Side Javascript with iText. This content explains what I discovered, because I couldn't find a similar post:

  1. Download the iText jar file from here.
  2. From 'Window' menu select 'Show Eclipse Views', 'Other', 'Package Explorer', to add the jar file to this folder of your database:
  3. Create a PDF file as a base template. I did this in Symphony as it saved the complication of adding lines and graphics programmatically. You then overlay text on this PDF to create each file.
  4. Call the following SSJS function to produce the PDF and attach it to a rich text field in a document:
//A. Load the java packages
importPackage(com.itextpdf);
importPackage(com.itextpdf.text);
importPackage(com.itextpdf.text.pdf);
importPackage(com.itextpdf.text.pdf.fonts);
importPackage(java.io);

//B. Initialize and get the PDF template (eg: from the Notes Data directory)
var theDirectory= session.getEnvironmentString("Directory",true);
var reader = new com.itextpdf.text.pdf.PdfReader (theDirectory + "/Template.pdf");
var fileOut = new java.io.FileOutputStream(filename);
var stamper = new com.itextpdf.text.pdf.PdfStamper(reader, fileOut);
var canvas = stamper.getOverContent(1);

//C. Embed the fonts you intend to use
var arial = new BaseFont.createFont("c:/windows/fonts/arial.ttf", BaseFont.WINANSI, BaseFont.EMBEDDED);
var arialbd = new BaseFont.createFont("c:/windows/fonts/arialbd.ttf", BaseFont.WINANSI, BaseFont.EMBEDDED);
var NORMAL = new Font(arial,10);
var BOLD = new Font(arialbd,10);
var BOLD11 = new Font(arialbd,11);
var BOLD12 = new Font(arialbd,12);

//D. Create and place your data eg:
var client = new Phrase(doc.getItemValueString("Client"),NORMAL);
ColumnText.showTextAligned(canvas, Element.ALIGN_LEFT, client, 65, 670, 0);
or
var lcost = new Phrase(@Text(doc.getItemValueDouble("Cost"),"C,"), BOLD11);
ColumnText.showTextAligned(canvas, Element.ALIGN_RIGHT, lcost, 563, 533 - (i*44), 0);
Note that the coordinates are in points, starting from the bottom-left of the page. Zero rotation. 1" = 72 points = 25.4mm

//E. Close and Attach
stamper.close();
fileOut.close();

//attach the pdf
var rtitem:NotesRichTextItem= doc.createRichTextItem("Invoice");
rtitem.embedObject(NotesEmbeddedObject.EMBED_ATTACHMENT, "", filename, null);

For more detailed information this book can be purchased here:


    Friday 10 June 2011

    How to email a doclink to an XPage document

    Workflow applications are a common strength of Lotus Domino. This post explains how to send workflow emails from XPage applications.

    There are two situations:
    1. Your document and XPage are in the same database;
    2. Your Xpages are in a different database to your documents.
    1. In the first case you can still send doclinks, like you have always done. However, you do need to set a new property on the Notes Form to ensure that when the doclink is activated, it will render the document in the correct XPage:

    Set the property 'Display XPage instead' to ensure doclinks do not open using the raw form.

    2. The ability to separate the code from the data is a strength of XPages. But it is then no longer possible to set the property on a document's form when the XPage is not in the same database. We need to send a link using mime, as you would have done in earlier versions of Notes. Here is the SSJS version:

    var body = memo.createMIMEEntity();
    var stream = session.createStream();
    var info = <see below>;
    stream.writeText(info);
    body.setContentFromText(stream, "text/html; charset=iso-8859-1", 0);

    In this case we simply want the email recipient to see a message about their role in the workflow, followed by a hotlink (Open Link) to open the appropriate document:

    var info = msg + "<br /><br /><a href='"+applicationScope.serverURL + "/apps/code.nsf/xProposal.xsp?databaseName="+applicationScope.serverCN+"!!"+ applicationScope.dataDbName+"&documentId="+ doc.getUniversalID()+"&action=openDocument &content= docs_form'>Open Link</a>";

    Application scope variables define the url of the server where the XPage is located and another to define the common name of the server where the database that contains the document is located. You can see that the link is to the XPage, xProposal.xsp, with the parameter, databaseName, defining the server and database, then the documentId parameter defining the document's universal id. The action parameter determines whether to open in read mode or edit mode.

    You do need to apply Fixpack 2 to your 8.5.2 Domino servers if you wish to store your XPages in a datatbase that is in a different directory to the databases containing your documents and then be able to access this from the Notes client.

    Thursday 9 June 2011

    How to create column totals in XPages view and repeat controls

    In a Notes view it is fairly easy to add a column total, but how is this done in an XPages view control, or a repeat control ? It took me a trip to Lotusphere 2011 to find out.
    Example:

    We need the totals to: 
    1. stay right-aligned under the column that is being totalled, even when the table is being resized.
    2. to show the total of the values from the documents that are being displayed on the page

    1. For this example we need to add a panel to the <xp:this.facets> tag of the view or repeat control:
    <xp:viewPanel id="viewPanel1">
         <xp:this.facets>
              <xp:panel xp:key="footer" id="totals1">
                   <xp:tr>
                       <xp:td colspan="7"></xp:td>
                       <xp:td>
                            <xp:text escape="true" id="computedField1">
                        </xp:td>
                       <xp:td colspan="2"></xp:td>
                       <xp:td>
                            <xp:text escape="true" id="computedField2">
                        </xp:td>
                       <xp:td>
                            <xp:text escape="true" id="computedField3">
                        </xp:td>
                     </xp:tr>
                 </xp:panel>
           </xp:this.facets>
    The trick is in the xp:key property of the panel. Setting this to "footer", as is done for a pager control that you wish to have appear in the footer, allows you to add the extra row. Into this last row of the table that forms the view or repeat control, we place the computed fields. (Note that this example assumes that there are 7 columns before the column titled 'Value')

    2. Lets assume that we provide a combo box on the XPage to allow the user to filter the documents that will display in the view or repeat control.


    With Firebug, we can see that XPages creates a dynamic id and name for each component. For the combo box it is view:_id1:_id2:_id94:comboBox1.
    But notice that all components on a custom control are allocated the same prefix of 'view:_id1:_id2:_id94:' to the name we gave the component:
    So we can add the following client-side Javascript code to the 'On Change' event of our combo box:
       var fcast = 0;
       var i = 0;
       var id_prefix = "#{id:comboBox1}".split("comboBox1")[0];
       while (dojo.byId(id_prefix+"repeat1:"+i+":inputText3")) {
           var it3= dojo.byId(id_prefix+"repeat1:"+i+":inputText3").innerHTML;
           it3 = it3.replace("$","").replace(/,/gi,"");
           fcast = fcast + parseFloat(it3);
           i++;
       }
       var tot = formatCurrency(fcast);
       dojo.byId(id_prefix+"computedField1").innerHTML = tot;
    The third line uses "#{id:comboBox1}" to return the full id allocated to the comboBox1 field at run time. Please note that this code does not work if placed in a script library.
    The while loop starts from the 0 row (first) and loops for every row on the view/repeat control where the inputText3 field exists.
    Firebug shows that the values are rendered as span tags, so we need to use .innerHTML to get and set values.
    The $ and commas need to be removed from the strings using the replace method, so the totals can be accumulated.
    Finally, the formatCurrency function rounds to two decimal places and reformats the total with the $ and the commas. (This is a custom function, not shown here.)
    Similar code can be created for computedField2 & computedField3.
    So, if your customers need a report that shows column totals, or even need a report with filters that show totals, you can make use of these techniques.
    Shout out to Paul Hannan and Marie Kehoe for their being the catalyst for this solution.

    Wednesday 8 June 2011

    Memory Management in XPages

    So, I am sitting on the coach to the 2011 Lotusphere party at the new Harry Potter World. Who should be sitting in the seat behind me, but the estimable Jeremy Hodge. We chatted, as you do, about memory management in XPages.
    Several years ago, a major customer was having problems as a result of a significant Notes application that was crashing their production application servers. They had outsourced the development of this mission critical application to an organisation who had used contract Notes developers to write Java agents. These developers hadn't had this chat with Jeremy !
    As a LotusScript developer, you would naturally want to convert your existing code to process through the documents in a view, to server-side Javascript as follows:
    var aView:NotesView = database.getView("SomeViewName");
    var doc:NotesDocument = aView.getFirstDocument();
    while (doc)  {
    // do some processing on doc
    doc = aView.getNextDocument( doc );
    }
    However, this code could cause the problem that my customer was experiencing, if there were a large number of documents in the view.
    In XPages, the above code needs to be written like this:

    var aView:NotesView = database.getView("SomeViewName");
    var doc:NotesDocument = aView.getFirstDocument();                               // line 1
    while (doc)  {
    // do some processing on doc
    var nextdoc:NotesDocument = aView.getNextDocument( doc );  // line 2
    doc.recycle();                                                                                          // line 3
    var doc:NotesDocument = nextdoc;                                                   // line 4
    }
    Jeremy explained to me that in the first case a Notes document object is instantiated in memory for each document in the view. 'doc' is only a pointer to the last object created. In the case where there are many documents in the view, the memory on the Domino server eventually fills up, the servers slows and eventually can crash. Bad, bad, bad !
    At 'line 1' above, the first Notes document object gets instantiated in memory, with doc created as a pointer to that memory location. At line 2 a second Notes document object gets instantiated in memory, with nextdoc created as a pointer to that memory location. Line 3 sets the first document object for garbage collection. Line 4 creates a new doc pointer and sets it's value to the memory location pointed to by nextdoc. The two pointers, doc and nextdoc now both point to the only Notes document object being retained in memory.
    As we go round the while loop, processing through the view, a new Notes document object gets instantiated, with nextdoc set to it's memory location; the object pointed to by doc gets set for garbage collection and doc is then set to point to the same memory location as nextdoc. Voila ! Only ever a maximum of two Notes document objects, with all their methods and properties, taking up memory.
    If you want to be really good, you could add this statement after the while loop:
    aView.recycle();
    When using objects in an agent, all objects are destroyed when the agent ends. When using servlets, .jsp's, or standalone applications, recycle must be used since Domino will never clean up these backend objects.

    Warning: never recycle an object that you have open in the UI. This will generate the error "object has been recycled or removed" and you will have to use 'trial and error' to determine which one you inadvertently recycled !
    There are just so many benefits from attending Lotusphere. Some that you can't even anticipate !

    Getting Started with XPages

    So you're a LotusScript developer and want to extend your knowledge to be able to build applications in XPages for Domino 8.5.x. Where do you start ?
    There is no point trying to learn something new without a purpose, so find a small project that you will build in XPages. Important: ensure you only pick something simple ! There are quite a few new things to learn, so you don't want to be overwhelmed by complex business logic when you are trying to create your initial kit-bag of resources. Remember back to when you started with LotusScript - it took a while to collect those script libraries of reusable code before you became really productive. It is the same with XPages.
    There are some excellent resources (mostly free) made available by the XPages community. Start here: XPages.info
    Here you can download the 8.5.2 designer client (for free). Note the 'Forum' link at the top, where you can ask questions. There are some excellent 'Demos' where you can download real code to examine, and the 'Resources' links provide a wealth of knowledge. An incredible site, don't you think ? I can also recommend David Leedy's Notes-in-9 videos from XPages.tv
    Once I had my initial project selected, I enrolled in the View's XPages Bootcamp. This is a three-day, instructor-led seminar delivered by Paul Calhoun & Russ Maher. Being someone who learns best with face-to-face contact, where I can ask questions and write code, this suited me perfectly. They assumed no prior knowledge beyond what a Notes Developer will know. You take your own laptop to do the workshop exercises, so you have a fantastic collection of working code to take home. With a focus on the project I was planning to do, I was able to ask all the right questions to help get me started. This really maximised the value from attending the course, given I had travelled from Australia to Copenhagen. (What a beautiful city in December !)
    Timing my first XPages project to go into production in early February enabled me to take any issues to Lotusphere in January. I had plenty !
    If you have never been to Lotusphere in Orlando, you need to go. This five-day conference is so good, that I will pay for it myself if my company doesn't send me. One visit to the 'Meet the Developers' lab where you can actually talk to the people who code and test the IBM software stack, provided solutions to all my outstanding XPages issues. The time spent with Marie Kehoe & Paul Hannan justified the cost of the conference. Everything else was a bonus. Meeting with Tim Clark in the Collaborative Solutions Development Lab for some one-on-one discussions about custom controls in XPages was another highlight. There were  more than 20 sessions to attend about XPages, so my knowledge and kit-bag of resources increased considerably. The joy of finding the famous Jeremy Hodge sitting just behind me on the coach to Harry Potter World, for the Lotusphere party, was a fantastic opportunity to learn about memory management in server-side Javascript. (I'll share that with you in my next blog post.) There is no better value training than attending those five days at Lotusphere each January.
    A more complex XPages project was built upon the simple workflow application I had used to 'cut my teeth' over the Christmas/New Year period. This went live in May 2011. I will share some of the things I learned during this journey in later blog posts.
    I have just purchased and completed the first seven lessons from XPages 101Matt White is an incredibly good instructor, so I can strongly recommend this course. There are currently 54 lessons, with more being added all the time. Because June is 'Learn XPages Month' we receive a 33% discount on a twelve-month subscription to these brilliant resources. Lesson 22 is free, if you want to see for yourself how good this course really is. It contains so many fantastic tips and more code to add to our kit-bags !
    In six short months, I now have the confidence that I am as productive with XPages development as I am with LotusScript. It's a wonderful feeling !

    Giving Back to the XPages Community

    XPages has a wonderful community. So many people have helped me to break through the initial frustrations, where after 12 years of LotusScript development, I felt like a beginner once again.
    As a result of all this support, my first 'built-from-scratch' XPages application went into production in May 2011, with complex workflow, automatic PDF and Excel export, using OneUIv2.
    This blog is a way to give back to the XPages community; to share some of the solutions I have discovered; and to help you to quickly become productive with this fantastic new tool.
    If you can see any improvements to the code that I provide, please add your comments. Your feedback is also most welcome.