Posts Tagged: ‘9.0’

iNotes still shows old design after upgrading mailfiles with german mailtemplate to version 9.0

30. September 2013 Posted by Stephan Kopp

This bug is well known since the release of Domino 9 language packs, but I still can’t find a new download with a fixed mailtemplate version…

Currently many customers in Germany are upgrading to version 9 and may now getting into trouble because of this issue. So I post again this solution found in the IBM Forum, but with a little bit different coding.

The problem:
After upgrading a Domino server to version 9.0, your users might also want to use a german mailtemplate with version 9 features. That’s not a big deal, just put the german mailtemplate to your server and upgrade the maildatabase design. But this DOES NOT work for iNotes, users will still see the old version 8.5 iNotes!

The reason:
There is a hidden field in the database icon called “$FormsTemplateFile”, which provides the filename of the Forms.nsf to be used within iNotes. This Field should contain “iNotes/Forms9.nsf” to use the new version of iNotes, but sadly it contains “iNotes/Forms85.nsf”.

The solution:
The fastes solution is to delete the Forms85.nsf from your server and immediatelly all  users will see the new and very nice iNotes.

But in many cases, you don’t want to upgrade the userinterface of iNotes for all users without providing them also the new client and new mailtemplate. That’s why you should fix the bug in the german mailtemplate. Then you can provide your users a broad upgrade of all userinterfaces in one step (Notes Client, Mailtemplate + iNotes).

1. Step: Download my tool and open it

2. Step: Open the tool, press the button and apply the fix to your template

3. Step: Put the template to your server (sign it if necessary)

4. Step: Test the new template and then use it during your upgrade

DISCLAIMER: Techniques and code provided here are not guaranteed or warranted in any way and are free for you to use at your own risk


Filed under: IBM Notes/Domino

Quick-n-Dirty: Ajax Fileuploads

9. September 2013 Posted by Sven Hasselbach

Here is an example how to upload a file with Ajax to a XPage. It is a simple Javascript, and adds the required fields to the xhr request. This example works in FireFox and Chrome and should work on Domino >=8.5.2.

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">

    <xp:this.data>
        <xp:dominoDocument
            var="document1"
            formName="RTItem"
            action="editDocument"
            concurrencyMode="force">
        </xp:dominoDocument>
    </xp:this.data>

    <xp:fileUpload onchange="handleFiles(this.files)"
        id="fileUpload1"
        value="#{document1.Body}">
    </xp:fileUpload>

    <xp:eventHandler event="onsaveDoc" submit="false" refreshMode="norefresh" immediate="false" save="true" id="saveDoc" />

    <xp:button
        value="ajaxSave"
        id="buttonAjax">
        <xp:eventHandler
            event="onclick"
            submit="false">
            <xp:this.script><![CDATA[sendFiles();]]></xp:this.script>
        </xp:eventHandler>
    </xp:button>

    <xp:div id="fileData" />

      <xp:scriptBlock id="scriptBlockAjax">
          <xp:this.value>
          <![CDATA[

          function handleFiles(files) {
            var i = 0;
            var dataDiv = dojo.byId('#{id:fileData}');
             var fileList = files;

            for(i = 0; i < fileList.length; i++)
            {
                    var img = document.createElement("img");
                    img.file = fileList[i];
                    img.name = 'no_'+ i;
                    img.classList.add("fileData");

                    var reader = new FileReader();
                    reader.onload = (function(aImg) { return function(e) { aImg.src = e.target.result; }; })(img);
                    reader.readAsDataURL(fileList[i]);

                    dataDiv.appendChild(img);    
            }
        }

        function FileUpload(img, file) {
             var xhr = new XMLHttpRequest();
            this.xhr = xhr;

            var frm = new FormData;
            frm.append( "#{id:fileUpload1}", file);
            frm.append( "$$viewid", dojo.byId('view:_id1__VUID').value);
            frm.append( "$$xspsubmitid", "#{id:saveDoc}");
            frm.append( "view:_id1", "view:_id1");

            xhr.open("POST", "#{javascript:facesContext.getExternalContext().getRequest().getRequestURI()}", true);
            xhr.send(frm);
        }

        function sendFiles(){
          var files = document.querySelectorAll(".fileData");

          for(var i = 0; i < files.length; i++)
          {
              new FileUpload(files[i], files[i].file);
          }

        }
        ]]>
        </xp:this.value>
    </xp:scriptBlock>

</xp:view>

The fileupload control executes the javascript method handleFiles when a new file is selected. This method “converts” the file to a DOM image and attaches it to the DIV fileData.

When clicking the button ajaxSend, the sendFiles method is fired and sends all selected files with xhr Requests to the server. The value of the $$submitid identifies a server side event which saves the document with the received attachment as new document.

Keep in mind: There is NO full refresh.

If you selecting some file in the browser…

.. and click on “ajaxSave“, the files are posted to the server:

The server side event saves the uploaded files to new documents:

XPages: File downloads and blocked UI

24. Juli 2013 Posted by Sven Hasselbach

Yesterday Christian asked a very interesting question: He had observed that the browser UI is blocked when clicking a button which generates a PDF on the server and sends the result. For about 30 seconds no button is working, no events are fired. My first thought was that this behaviour is caused by the submit locking during partial refreshs, and after testing a XSP.allowSubmit() in the debug console I could prove myself that I was right.

It looked first like an easy solution, but after thinking a little bit about it, I had no idea how to execute this little piece of CSJS code. Let’s have a look at the PDF generation:

public void exportPdfOutputStream(TemplateData pdfData, String templateFile, String filename) {

   ExternalContext con = facescontext.getExternalContext();
   XspHttpServletResponse response = (XspHttpServletResponse) con.getResponse();
   try {
     ServletOutputStream writer = response.getOutputStream();
     // setting response headers for browser
     response.setContentType(“application/pdf”);
     response.setHeader(“Cache-Control”, “no-cache”);
     response.setDateHeader(“Expires”, -1);
     response.setHeader(“Content-Disposition”, “attachment; filename=\”" + filename + “\”");

     PdfReader pdfTemplate = getPdfReader(templateFile);
     PdfStamper stamper = new PdfStamper(pdfTemplate, writer);
     stamper.setFormFlattening(true);
     setDataToPdfDocument(pdfData, stamper);
     stamper.close();
     pdfTemplate.close();
     writer.flush();
     writer.close();
     facescontext.responseComplete();
    } catch (Exception e) {
      String errorMessage = “PDF Exporter: An error occureed: ” + e.toString();
      try {
         response.sendError(500, errorMessage);
      } catch (Exception e2) {
        e2.printStackTrace();
      }
    }
 }

A full refresh is submitted to the server, the code generates a PDF, adds the result to the output stream as attachment, and then closes the response. The browser receives a file to download and that’s it. Works fine. But how to execute some CSJS code? You cannot use view.postScript nor you cannot send an additional HTML element (a JS script block) and there is no onComplete event.

While I was musing I had the idea to use a repeating interval and check the server response in Javascript. But how can you access this information? When performing a XHR Call (a partial refresh) you can access the HTTP header of the response, but this is not possible when performing a full refresh.

I googled for an answer an found this article about a solution to block the UI: http://geekswithblogs.net/GruffCode/archive/2010/10/28/detecting-the-file-download-dialog-in-the-browser.aspx

It uses a cookie to send the information back to the browser, which is a really amazing idea. Here is my implementation for XPages and Dojo 1.8:

The button to download the PDF first executes a CSJS script which fills in the field fileDownloadToken with the current timestamp. This timestamp is sent to the server and then added to the cookie in the response.

The client checks every 500ms if the cookie exists and if the value equals the original token sent to the server. If the cookie is set correctly, the finishDownload() method is executed which calls XSP.allowSubmit() and removes the cookie.

This is the code of the XPage:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view
        xmlns:xp="http://www.ibm.com/xsp/core">
        <xp:button
                id="buttonDownload"
                value="Download">
                <xp:eventHandler
                        event="onclick"
                        submit="true">

                        <xp:this.action>
                                <![CDATA[#{javascript:
                                  importPackage( ch.hasselba.tools );        
                                  new ch.hasselba.tools.PDFUtil().exportPdfOutputStream("output.pdf");}]]>
                        </xp:this.action>
                        <xp:this.script><![CDATA[
                                require(["dojo/cookie"], function(cookie){
                                          var fileDownloadCheckTimer;
                                          var tokenName = 'fileDownloadToken';

                                        function finishDownload() {
                                                 window.clearInterval(fileDownloadCheckTimer);
                                                 cookie('fileDownloadToken', null, {expire: -1});
                                                 XSP.allowSubmit();                        
                                        }

                                        function startDownload(){
                                                setToken();
                                                fileDownloadCheckTimer = window.setInterval(function () {
                                                      var cookieValue = cookie( tokenName );

                                                      if ( cookieValue == getToken() )
                                                               finishDownload();
                                            }, 500);

                                        }

                                        function getToken(){
                                                return XSP.getElementById( tokenField ).value;
                                        }
                                        function setToken(){
                                                 XSP.getElementById( tokenField ).value = Date.now();
                                        }

                                        startDownload();
                                  })
                        ]]></xp:this.script>
                </xp:eventHandler>
        </xp:button>
        <input type="hidden" id="fileDownloadToken" name="fileDownloadToken" />

</xp:view>

And this is the modified Java code:

package ch.hasselba.tools;

import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.Cookie;

import com.ibm.xsp.webapp.XspHttpServletResponse;

public class PDFUtil {
        public void exportPdfOutputStream(String filename) {

                FacesContext fc = FacesContext.getCurrentInstance();
                ExternalContext ex = fc.getExternalContext();

                XspHttpServletResponse response = (XspHttpServletResponse) ex
                                .getResponse();

                // get the token from the request
                String token = (String) ex.getRequestParameterMap().get(
                                "fileDownloadToken");

                try {
                        ServletOutputStream writer = response.getOutputStream();

                        // setting response headers for browser
                        response.setContentType("application/pdf");
                        response.setHeader("Cache-Control", "no-cache");
                        response.setDateHeader("Expires", -1);
                        response.setHeader("Content-Disposition", "attachment; filename=\""
                                        + filename + "\"");

                        // set the cookie to the response
                        response.addCookie(new Cookie("fileDownloadToken", token));

                        //
                        // generate the output
                        //

                        // close the writer and mark response as completed
                        writer.flush();
                        writer.close();
                        fc.responseComplete();

                } catch (Exception e) {
                        e.printStackTrace();
                }
        }
}

Quick-n-Dirty: Upgrading XPiNC / XULRunner

17. Juli 2013 Posted by Sven Hasselbach

I have tested a small upgrade of XULRunner (the internal browser used for XPiNC applications) and successfully changed the version from 1.9.2.10 to 1.9.2.28 in DDE 8.5.3 and 9.0.

Version 1.9.2.10 was released on 15.10.2010
Release Notes for Firefox 3.6.10
Version 1.9.2.28 was released on 13.03.2012
Release Notes for Firefox 3.6.28

To do this by your own you have process the following steps:

  1. Close Notes and Designer completly
  2. Download the package for your platform from mozilla.org
  3. Extract the ZIP file
  4. Open the folder <NOTES>\framework\rcp\eclipse\plugins
  5. Open the folder com.ibm.rcp.xulrunner.runtime.win32.x86_6.2.3.20110915-1350 [The name of the folder depends on your DDE]
  6. Rename the folder xulrunner to xulrunner_bak (if you have problems with DDE you can switch back later by renaming the folder)
  7. Copy the xulrunner folder from the downloaded ZIP into this folder
  8. Restart Notes

P.S. Keep in mind that this article has been posted in the “Quick-n-Dirty” category.

XPages: Use a PhaseListener for global URL commands

11. Juli 2013 Posted by Sven Hasselbach

One of my customers wanted an easy way for printing XPages in different formats and with different content, depending of the current XPage opened in the browser. It was a requirement to develope a global solution for every XPage-based application in his company. That is why I created a global Java class which does not require to modify the existing applications at all. But it allows to customize and configure the output for the different needs of the departments and their required reports and print outs.

I decided to use a special PhaseListener to hook into the different applications. This allows the PDF generation depending on some URL parameters, and only runs if the URI contains a special command for printing the current XPage (but works for thw whole application).

The example code is what I have created:

package ch.hasselba.xpages.util;

import java.util.Map;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class PhaseListenerPDFGenerator implements PhaseListener {

    private static final long serialVersionUID = 1L;
    private static final String GENERATE_PDF = ".PDF";
    private static final String URL_PARAMETER_FILENAME = "filename";
    private static final String URL_PARAMETER_PDFTYPE = "type";
    private static final String HTTP_HEADER_CONTENTTYPE = "application/pdf";

    public void beforePhase(PhaseEvent event) {}

    public PhaseId getPhaseId() {
        return PhaseId.RESTORE_VIEW;
    }

    public void afterPhase(PhaseEvent event) {

        FacesContext facesContext = event.getFacesContext();

        HttpServletRequest req = (HttpServletRequest) facesContext
                .getExternalContext().getRequest();
        String uri = req.getRequestURI();

        if (uri.endsWith(GENERATE_PDF) == true ) {
            servePDF(facesContext);
        }
    }

    private void servePDF(FacesContext facesContext) {
        Map requestMap = facesContext.getExternalContext()
                .getRequestParameterMap();

        String pdfName = getPDFName(requestMap);
        String pdfType = getPDFType(requestMap);

        HttpServletResponse response = (HttpServletResponse) facesContext
                .getExternalContext().getResponse();

        try {

            response.setContentType( HTTP_HEADER_CONTENTTYPE );
            response.setHeader( "Content-Disposition", "attachment; filename="
                      + pdfName );
            response.setStatus(200);
            ServletOutputStream outputStream = response.getOutputStream();

            // Generate the PDF here
            // and send the data to the outputStream
            //
            //outputStream.write( PDFDATA );
            outputStream.flush();
            outputStream.close();
            facesContext.responseComplete();

        } catch (Exception exception) {
            exception.printStackTrace();
        }
    }

    public static String getPDFName(Map requestMap) {
        String pdfName = (String) requestMap.get( URL_PARAMETER_FILENAME );
        return pdfName;
    }
    public static String getPDFType(Map requestMap) {
        String pdfName = (String) requestMap.get( URL_PARAMETER_PDFTYPE );
        return pdfName;
    }

}

The PhaseListener scans the URI every time a request is sent to the server. As soon the URI ends with .PDF, the parameters fileName and type are extracted. The correct PDF template is identified and the requested PDF can be generated (Code for generating is not included in this example).

After generating the PDF, the output is written, and the response is completed. The file download headers are added to the response. The user receives a file download dialog and can open the generated PDF (and print it if required).

When this PhaseListener is enabled in the faces-config.xml, every XPage can use the URL command. For example if your XPage is named “MyXPage.xsp” the URL to enable the PDF generation instead the generation of the XPage looks like this:

http://example.com/path/to/db.nsf/MyXPage.xsp/.PDF?filename=test.pdf&type=exportAll

This returns a PDF with the filename test.pdf which is generated from the PDF template exportAll, containing the data of the current XPage. It is easy to add a link like this to your application to enable the PDF generation.

This is how the faces-config.xml has to look like:

<?xml version="1.0" encoding="UTF-8"?>
<faces-config>
  <lifecycle>
      <phase-listener>ch.hasselba.xpages.util.PhaseListenerPDFGenerator</phase-listener>
  </lifecycle>
</faces-config>

XPages: Create your own Required Validators

20. Juni 2013 Posted by Sven Hasselbach

If you try to implement your own JSF Validator (by implementing javax.faces.validator.Validator), you will notice that you are unable to check for an empty value. The reason for this is rather simple: The method validate() is only called, if there is something to do. If the value is empty, there is nothing to do, that’s why nothing happens.

But wait! What about the available required validator for XPages?

<xp:inputText id="inputText1">
   <xp:this.validators>
      <xp:validateRequired></xp:validateRequired>
   </xp:this.validators>
</xp:inputText>

And yes, this is a validator, but the IBM implemented a workaround for their UIComponents: If the UIComponents required property is set to true, the UIComponent knows that she needs to be filled in. Or a validator has to be attached to the UIComponent which implements the com.ibm.xsp.validator.FacesRequiredValidator interface.

Here is an example:

package ch.hasselba.xpages.core;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.validator.ValidatorException;
import com.ibm.xsp.validator.FacesRequiredValidator;

public class RequiredValidator implements FacesRequiredValidator {

    public String getRequiredMessage() {
        return "Error! Required field is empty!";
    }

    public void validate(FacesContext facesContext, UIComponent uiComponent,
            Object obj) throws ValidatorException {

    }

}

When processing the UIComponent data, the attached validators are checked if they are an instance of FacesRequiredValidator, and this is the reason why you cannot attach this validator to a component in the DDE directly: Because if you define the validator in the faces-config.xml and add it to the UIComponent with the validator id…

<xp:inputText id="inputText1">
   <xp:this.validators>
      <xp:validator validatorId="myValidator" />
   </xp:this.validators>
</xp:inputText>

… the XPages engine attaches a validator which is not implementing the FacesRequiredValidator interface. Instead, an instance of com.ibm.xsp.validator.ValidatorImpl is attached.

You have to do this by your own, for example in the beforeRenderResponse event:


<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">

   <xp:this.beforeRenderResponse>
      <![CDATA[#{javascript:getComponent('inputText1').addValidator(
         new ch.hasselba.xpages.core.RequiredValidator()
      );}]]>
   </xp:this.beforeRenderResponse>

   <xp:button
      value="Label"
      id="button1">
      <xp:eventHandler event="onclick" submit="true"
         refreshMode="complete" immediate="false" save="true" />
   </xp:button>

   <xp:br />

   <xp:messages id="messages1"></xp:messages>

   <xp:br />

   <xp:inputText id="inputText1" />

</xp:view>

[This example is just for a demonstration. It contains a bug, the validator will attached over and over again, so you have to check if the validator is already attached or not]

If you now open the XPage, your validator will work as expected:

 

XPages: Dojo 1.8.1 & jQuery Mobile 1.3.1

25. April 2013 Posted by Sven Hasselbach

As David Leedy got into trouble with Dojo 1.8.1 and jQuery Mobile 1.3.1  and after reading the follow up from Ulrich Krause with the analysis of the problem, I thought that this problem is caused from the AMD loader of Dojo. That’s why I changed the loading order (by forcing the jQuery libraries to get loaded before Dojo), and this seems to work.

I have tested it in IE 8, FF 20.0 and Chrome 26.0.

Here is my test XPage:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view
    xmlns:xp="http://www.ibm.com/xsp/core">
    <xp:this.properties>
        <xp:parameter
            name="xsp.resources.aggregate"
            value="true" />
    </xp:this.properties>
    <xp:this.resources>
        <xp:styleSheet
            href="http://code.jquery.com/mobile/1.3.1/jquery.mobile-1.3.1.min.css" />
        <xp:headTag
            tagName="script">
            <xp:this.attributes>
                <xp:parameter
                    name="type"
                    value="text/javascript" />
                <xp:parameter
                    name="src"
                    value="http://code.jquery.com/jquery-1.9.1.min.js" />
            </xp:this.attributes>
        </xp:headTag>
        <xp:headTag
            tagName="script">
            <xp:this.attributes>
                <xp:parameter
                    name="type"
                    value="text/javascript" />
                <xp:parameter
                    name="src"
                    value="http://code.jquery.com/mobile/1.3.1/jquery.mobile-1.3.1.min.js" />
            </xp:this.attributes>
        </xp:headTag>
    </xp:this.resources>

    <div
        data-role="page"
        class="jqm-demos"
        data-quicklinks="true">

        <div
            data-role="content"
            class="jqm-content"
            id="contentRefresh">
            <h2
                id="accordion-markup">
                <script>document.write(dojo.version)</script>
            </h2>

            <div>
                <div
                    data-role="collapsible-set"
                    data-theme="c"
                    data-content-theme="d">

                    <div
                        data-role="collapsible">
                        <h3>Section</h3>
                        <p>
                            <xp:label
                                value="#{javascript:java.lang.System.currentTimeMillis()}"
                                id="labelRefresh" />
                        </p>
                    </div>
                </div>
            </div>
            <a
                href="#"
                onclick="XSP.partialRefreshGet('#{id:labelRefresh}')"
                data-role="button">Refresh Label</a>
        </div>
    </div>

</xp:view>

And this is the result:

The XSP object is loaded correctly and Partial Refreshs are executed correctly:

It is required that the resource aggregation is enabled. More details can be found here.

Quick-n-Dirty: Dojo 1.8 & Domino 8.5.3

25. April 2013 Posted by Sven Hasselbach

If you want to use Dojo 1.8 with Domino 8.5.3, you can do the following:

1. Grab the Dojo JAR file from a ND9 installation:

<PATH TO DOMINO>\osgi\shared\eclipse\plugins\com.ibm.xsp.dojo_9.0.0<VERSION>.jar

2. Create a folder “dojo-1.8.0” in your notes data directory

<PATH TO DOMINO DATA>\domino\js\dojo-1.8.0

3. Unpack the JAR file

4. Copy the content of the subfolder \resources\dojo-version of the unpacked JAR file in this folder:

5. Restart HTTP task.

6. Enjoy!

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
    <script>document.write(dojo.version)</script>
    <br/>
    <xp:label value="#{javascript:
        importPackage(com.ibm.xsp.core);
        Version.CurrentRuntimeVersion}" />
</xp:view>

P.S. Keep in mind that this article has been posted in the “Quick-n-Dirty” category.

XPages: Add inline CSJS with a Theme

18. April 2013 Posted by Sven Hasselbach

I needed a way to add some inline CSJS to an existing application which affects any XPage, that’s why I did this them with a theme.

Et voilà:

<theme extends="webstandard" >

    <resources mode="concat">
        <script>
            <contents>alert('THEME JS!')</contents>
            <clientSide>true</clientSide>
        </script>
    </resources>

</theme>

The javascript code is added and executed as expected:

Notes 9: Some interesting xsp.properties

3. April 2013 Posted by Sven Hasselbach

Some interesting new xsp.properties were introduced with Notes 9:

  • xsp.client.resources.uncompressed

When set to true, all Dojo libraries and CSS resources where delivered in the uncompressed version. The path changes f.e. to /xsp/.ibmxspres/dojoroot-1.8.1-u/dojo/dojo.js.

  • xsp.client.script.dojo.html5attr

When set to true, the Dojo HTML5 Data attribute is added to all Dojo component on the XPages. Here is an example for a Date/Time field:

<input type="text"
   id="view:_id1:inputText1"
   name="view:_id1:inputText1"
   class="xspInputFieldDateTimePicker"
   data-dojo-type="ibm.xsp.widget.layout.DateTextBox"
   iconStyleClass="xspInputFieldDatePickerIcon"
   constraints="{datePattern:&quot;dd.MM.yyyy&quot;,timePattern:&quot;HH:mm:ss&quot;,selector:&quot;date&quot;}">
  • xsp.radiobuttongroup.item.label.prefixSpace

When set to true, a blank is added before the label the label:

<xp:radioGroup id="radioGroup1">
   <xp:selectItem itemLabel="Untitled" />
</xp:radioGroup>

Resulting HTML code (There is a space before the red marked label):

<label for="view:_id1:radioGroup1:0">
   <input type="radio" id="view:_id1:radioGroup1:0" 
   name="view:_id1:radioGroup1" value="Untitled"> Untitled</label>

New properties which are described in the xsp.properties.sample file:

  • xsp.maximum.mime.tree.scanLevel
  • com.ibm.ws.webcontainer.HTTPOnlyCookies
  • xsp.client.script.xspClient.preventLayer
  • xsp.client.script.radioCheckbox.ie.onchange.trigger
  • xsp.repeat.parseSingleStringAsInt
  • xsp.client.script.dojo.loader

Notes 9: No Comment!

26. März 2013 Posted by Sven Hasselbach