Posts Tagged: ‘web’

[EN] Only 10 to 20 percent of a company’s website content drives 90 percent of its web traffic – Forbes

11. September 2013 Posted by StefanP.

Interesting study:

Content marketing is a zero sum game, or so suggests a groundbreaking new content marketing study that should send shivers down the spines of business owners and content marketers alike. Only 10 to 20 percent of a company’s website content drives 90 percent of its web traffic, and only half a percent of a website’s content drives more than 50 percent of its traffic.

via Content Marketing Study Suggests Most Content Marketing Doesn’t Work – Forbes.


Filed under: English Tagged: Marketing, Web

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();
                }
        }
}

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: 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.

Web Development – Some nice tools

25. April 2013 Posted by Heiko Voigt

Hello, Lately, I have been working on an XPiNC Project in parallel to a project for a Web CRM System based on XPages. As we also created the web layout (and me not being the greatest designer at all ...

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:

XPages: Inject CSJS code at page top

9. April 2013 Posted by Sven Hasselbach

Sometimes it is required to add a some CSJS code at the beginning of the XPage before the Dojo libaries are loaded and are executed. This can be easily achieved by using the resource aggregation feature and including a CSJS library with the <xp:headTag>.

First you have to create a CSJS library which contains the code you want to execute:

Then you have to add the <xp:headTag> which loads the CSJS library:

<?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:headTag tagName="script">
             <xp:this.attributes>
                 <xp:parameter name="type" value="text/javascript" />
                 <xp:parameter name="src" value="Before.js" />
             </xp:this.attributes>
         </xp:headTag>
    </xp:this.resources>

</xp:view>

[The xp:parameter can ignored if resource aggregation is enabled for the whole application]

The generated HTML source code will look like this, with the CSJS library at the top of the page:

It works ONLY if resource aggregation is enabled. If it is disabled, the CSJS library is added after the Dojo libraries and style sheets:

Quick-n-Dirty: How to add HTML 5 events

26. März 2013 Posted by Sven Hasselbach

One way for adding unsupported events to an XPage or a component is the trick from Keith. But this is limited to CSJS only. If you need to execute a server side event, you just need change the name of the event to a new HTML 5 event name which does not exist in the DDE.

Here is an example for the new onSearch event:

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

    <xp:inputText id="inputTextSearch" type="search">
        <xp:eventHandler event="onsearch" submit="true"
            refreshMode="partial" refreshId="labelSearchFor">
            <xp:this.action>
               <![CDATA[#{javascript:print("onSearch");}]]>
            </xp:this.action>
        </xp:eventHandler>
    </xp:inputText>

    <xp:br />
    <xp:br />

    <xp:label id="labelSearchFor">
        <xp:this.value>
           <![CDATA[#{javascript:getComponent("inputTextSearch").value}]]>
        </xp:this.value>
    </xp:label>

</xp:view>

After entering a value and hitting enter…

… you can see on the server console that the event occurred:

This technique allows to add new events to the whole XPage, for example the onContextMenu event:

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

        <xp:eventHandler event="oncontextmenu" submit="true"
            refreshMode="norefresh">
            <xp:this.action>
                <![CDATA[#{javascript:print("onContextMenu");}]]>
            </xp:this.action>
        </xp:eventHandler>

</xp:view>

Every time if the user opens the context menu in the browser, the event is triggered and executed on the server:

The name of the event must be lowercased. A list of HTML 5 events can be found here: http://www.tutorialspoint.com/html5/html5_events.htm. Not all browsers support every HTML 5 event in the list.

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

XPages: UI for editing localization files (1)

17. Dezember 2012 Posted by Sven Hasselbach

For one of my customers I am developing an easy way for editing the localization/translation of existing XPages applications: Instead of importing and exporting the property files with the Domino Designer (which is not an option for non-developers) it would be more suitable, if the users would have the possibility to edit the files directly in the browser. I am currently  working on a XPage solution for this, and this is my current beta version.

This is a proof-of-concept with some known bugs, f.e. HTML special chars are not encoded correctly. But this can be fixied easily. First, it is required that the users can choose a property file to edit. This can be realized with a small Java class I have created (based on a stackoverflow.com answer):

package ch.hasselba.xpages.localization;

import java.util.Vector;
import javax.faces.context.FacesContext;
import lotus.domino.Database;
import lotus.domino.Document;
import lotus.domino.NoteCollection;
import lotus.domino.NotesException;

public class DesignElements {

    private final String EMPTY_STRING = "";
    private final String[] FLAG_PROPERTIES = { "gC~4K2P", "gC~4K2" };
    private final String FIELD_$FLAGS = "$Flags";
    private final String FIELD_TITLE = "$TITLE";

    /**
     * returns Vector containing all property files of a database
     * 
     * No error handling included!
     * 
     * @category Domino
     * @author Sven Hasselbach
     * @category Tools
     * @version 0.2
     */
    public Vector<String> getPropertFileList() {

        FacesContext fc = FacesContext.getCurrentInstance();

        Vector<String> data = new Vector<String>();
        try {

            // get DB
            Database db = (Database) fc.getApplication().getVariableResolver()
                    .resolveVariable(fc, "database");

            // get all design docs
            NoteCollection nc = db.createNoteCollection(false);
            nc.selectAllDesignElements(true);
            nc.buildCollection();

            // process all notes
            String noteId = "";
            noteId = nc.getFirstNoteID();

            Document doc = null;
            // 
            while (!(EMPTY_STRING.equals(noteId))) {

                // get design doc
                doc = db.getDocumentByID(noteId);

                // check if its a property file
                for (int i = 0; i < FLAG_PROPERTIES.length; i++) {
                    if (FLAG_PROPERTIES[i].equals(doc
                            .getItemValueString(FIELD_$FLAGS))) {
                        // add to Vector
                        data.add(doc.getItemValueString(FIELD_TITLE));
                    }
                }

                // next one
                noteId = nc.getNextNoteID(noteId);

                // recycle doc
                recycleObject(doc);
            }

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

        return data;

    }

    /**
     * recycles a domino document instance
     * 
     * @param lotus
     *            .domino.Base obj to recycle
     * @category Domino
     * @author Sven Hasselbach
     * @category Tools
     * @version 1.1
     */
    public static void recycleObject(lotus.domino.Base obj) {
        if (obj != null) {
            try {
                obj.recycle();
            } catch (Exception e) {
            }
        }
    }
}

The difference to the answer on stackoverflow.com is the additional property flag I have added to this class (I know it would be better to check the flags instead of comparing the strings, but it seems to work this way).

Next step is to load the selected property file. This can be done with a managed bean and a helper class for the entries itself. Here is the helper class:

package ch.hasselba.xpages.localization;

import java.util.UUID;

/**
 * Locale Entry of a locale file
 * 
 * @author Sven Hasselbach
 * @category Localization
 * @version 1.0
 * 
 */

public class LocaleEntry {

    private String id;
    private String name;
    private String value;

    /**
     * initializes the object and
     * sets an unique identifier
     */
    public LocaleEntry(){
        this.id = UUID.randomUUID().toString();
        this.name = "";
        this.value = "";
    }

    /**
     * returns unique identifier of the object
     * @return String unique id
     */
    public String getId() {
        return id;
    }

    /**
     * sets the unique identifier of the entry
     * @param id String unique id
     */
    public void setId(final String id) {
        this.id = id;
    }

    /**
     * returns the name of the entry
     * @return String
     */
    public String getName() {
        return name;
    }

    /**
     * sets the name of the entry
     * @param String
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * returns the value of the entry
     * @return String
     */
    public String getValue() {
        return value;
    }

    /**
     * sets the value of the entry
     * @param value
     */
    public void setValue(String value) {
        this.value = value;
    }

}

And here comes the “Loader” class:

package ch.hasselba.xpages.localization;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Vector;
import com.ibm.xsp.context.FacesContextEx;

/**
 * "Loader" class to access the entries
 * of a .property file
 * 
 * @author Sven Hasselbach
 * @version 1.0
 */
public class Locale {

    private Vector<LocaleEntry> locales;

    /**
     * @return vector containing the LocalEntry objects
     */
    public Vector<LocaleEntry> getLocales() {
        return locales;
    }

    /**
     * sets vector containing the LocalEntry objects
     * @param Vector
     */
    public void setLocales(Vector<LocaleEntry> locales) {
        this.locales = locales;
    }

    /**
     * wrapper for the static method call
     * 
     * @param fileName name of the property file to load
     */
    public void loadFile(final String fileName) {
        Map<String, String> m = getPropertiesFromFile(fileName);
        this.locales = parseMap(m);
    }

    /**
     * loads a property file and parses it to a key/value map
     * 
     * @param fileName name of the property file to load
     * @return Map containing the key/values
     * 
     * The loading routine is shamelessly copied from Ulrich Krause:
     * http://openntf.org/XSnippets.nsf/snippet.xsp?id=access-.properties-files
     */
    public static Map<String, String> getPropertiesFromFile(String fileName) {
        Properties prop = new Properties();

        try {
            prop.load(FacesContextEx.getCurrentInstance().getExternalContext()
                    .getResourceAsStream(fileName));
            Map<String, String> map = new HashMap<String, String>((Map) prop);

            return map;

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

    }

    /**
     * parses a property map and create a vector
     * with LocalEntry objects
     * 
     * @param map to parse
     * @return Vector containing the LocalEntry objects
     */
    public static Vector<LocaleEntry> parseMap(final Map<String, String> map) {

        // init new vector
        Vector<LocaleEntry> localEntries = new Vector<LocaleEntry>();
        String key;
        String value;

        // get key set for iteration
        Iterator<?> it = map.keySet().iterator();

        while (it.hasNext()) {

            // extract key & value
            key = (String) it.next();
            value = (String) map.get(key);

            // create new entry and add to vector
            LocaleEntry lEntry = new LocaleEntry();
            lEntry.setName(key);
            lEntry.setValue(value);
            localEntries.add(lEntry);

        }

        // return vector
        return localEntries;

    }

    /**
     * dumps current object data to console
     * Just for debugging
     * 
     * @category Debug
     */
    public void dump() {
        for (int i = 0; i < this.locales.size(); i++) {
            LocaleEntry e = this.locales.get(i);
            System.out.println(e.getId() + " - " + e.getName() + " - "
                    + e.getValue());
        }

    }

}

Now the new managed bean can be added to the faces-config.xml:

<managed-bean>
  <managed-bean-name>locale</managed-bean-name>
  <managed-bean-class>ch.hasselba.xpages.localization.Locale</managed-bean-class>
  <managed-bean-scope>session</managed-bean-scope>
</managed-bean>

The resulting XPage can look like this:

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

    Property File:&#160;
    <xp:comboBox id="comboBoxFile" value="#{sessionScope.PropertyFile}">
        <xp:selectItems>
            <xp:this.value>
            <![CDATA[#{javascript:
               importPackage(ch.hasselba.xpages.localization);
               ch.hasselba.xpages.DesignElements().getPropertFileList();
            }]]>
            </xp:this.value>
        </xp:selectItems>
    </xp:comboBox>
    &#160;
    &#160;
    <xp:button value="Load" id="buttonLoadFile">
        <xp:eventHandler event="onclick" submit="true"
            refreshMode="complete">
            <xp:this.action>
               <![CDATA[#{javascript:
                  importPackage(ch.hasselba.xpages.localization);
                  var bean = facesContext.getApplication().getVariableResolver()
                     .resolveVariable(facesContext, "locale");
                  bean.loadFile(sessionScope.get("PropertyFile") )}]]>
            </xp:this.action>
        </xp:eventHandler>
    </xp:button>
    <hr />
    <xp:label id="labelSelecteFile">
        <xp:this.value>
           <![CDATA[#{javascript:sessionScope.get("PropertyFile")}]]>
        </xp:this.value>
    </xp:label>
    <xp:br />
    <xp:br />
    <xp:repeat id="repeat1" rows="9999" var="lMap" value="#{locale.locales}">
        <xp:label value="#{lMap.name}" id="labelMapName" />
        <xp:inputText id="inputTextValue" value="#{lMap.value}" />
        <xp:br />
    </xp:repeat>

</xp:view>

And this is how it looks in the browser:

The next article describes the procedure to save the properties back the database.

XSnippets: viewPanelHelper

28. November 2012 Posted by Sven Hasselbach

I have added a new XSnippet, the viewPanelHelper. The code helps to keep the selection of the selected documents in a view panel even if pager is used or categories are expand or collapsed.

It is not required to modify the view: Only the script and the hidden field must be added to the facets section of the panel and the CSJS class “viewPanelHelper” must be added to the XPage (the class is loaded only once and can be stored in a separate CSJS resource file).

The selected documents are stored in the hidden field “viewPanelSelectedIds”.
Tested in IE 9 and FF 15 – 17.

Interview Maria Gomez mit Ivo Körner zu Führung 2.0

14. November 2012 Posted by Maria Gomez

 

Führung im Social Business: „Der Abstand in der Vertikale wird geringer"


Immer mehr Unternehmen in Deutschland haben mit der Transformation in ein Social Business begonnen. Soziale Netzwerke und Tools wie Team-Rooms, Blogs, Wikis oder Profile verändern die Art und Weise der Zusammenarbeit drastisch. Was diese Veränderung für Führungskräfte bedeutet, habe ich meinen Kollegen Ivo Körner, VP Software Group bei der IBM Deutschland, gefragt.


Maria Gomez: Deutsche Unternehmen wie Bosch, Continental oder Bayer drücken auf ihrem Weg ins Social Business mächtig aufs Gas. Was verändert sich hier für die Mitarbeiter ganz konkret in ihrer täglichen Arbeit?

Ivo Körner: Vor allem können sie sehr viel produktiver arbeiten. Klassische Zeitdiebe wie E-Mails, Telefonkonferenzen oder das Handling von Dokument-Versionen werden sie sehr viel weniger belästigen. Das ist aber nur ein Aspekt der Transformation. Ein anderer betrifft die gesamte Arbeitskultur. Bislang war Arbeiten ein lineares Abarbeiten einzelner, von den Vorgesetzten geschnürter Arbeitspakete. Jeder Mitarbeiter hatte eine ganz bestimmte Vorgabe zu erfüllen. Dieser starre Prozess wird in einem Social Business aufgeweicht, genauso wie die alten hierarchischen Strukturen in Abteilungen. Denn mit der Facebookisierung ziehen auch die Regeln des Mitmach-Netzes in die Unternehmen ein. Aufgaben werden nicht mehr stillschweigend erledigt, sondern gemeinschaftlich und im stetigen Dialog mit der Community. Zugleich werden die Aufgaben sehr viel stärker in Form von Projekten organisiert, für die sich spezielle Teams zusammenfinden und nach erfolgreicher Erledigung wieder auseinander gehen. Für die Mitarbeiter bedeutet das sicherlich eine enorme Umstellung, aber zugleich wird der Job dadurch abwechslungsreicher und eigenverantwortlicher und damit auch attraktiver für diejenigen, die es nicht mehr anders kennen, sprich: die Digital Natives.


Maria Gomez:Welche Rolle spielt die Führungsperson angesichts dieser Veränderung? Was ändert sich im Verhältnis zwischen Führungskraft und Mitarbeiter?

Ivo Körner: Der Abstand in der Vertikale wird geringer, die Zusammenarbeit zwischen Führungskraft und Mitarbeiter geschieht mehr und mehr auf Augenhöhe. Führungskräfte müssen in viel stärkerem Maße als zuvor Team-Player sein -- das ist ein entscheidender Unterschied zu den alten Verhältnissen. Führungsqualitäten werden sehr viel weniger an äußeren Symbolen festgemacht, sondern an der Sachkompetenz und an sozialen Kriterien wie Teamfähigkeit und Kommunikationsstil. Von beiden Seiten wird ein Stück weit erwartet, Teile des jeweils anderen Rollenmusters zu übernehmen: Die Führungskraft muss kritikfähig und offen sein. Der Mitarbeiter arbeitet hingegen viel eigenständiger, muss aber zugleich seine Arbeitsergebnisse transparenter und messbarer machen und dafür einstehen.


Maria Gomez: Besteht damit nicht die Gefahr, dass Führungskräfte ihre Verantwortung ans Team delegieren? Wie gestaltet sich die Rolle einer Führungskraft 2.0 genau?

Ivo Körner: Führungskräfte sollten vor allem das machen, was eigentlich ihre natürliche Rolle ist: mit gutem Beispiel vorangehen, also: Leading by example. Viele Nicht-Digital-Natives unter den Managern müssen daher lernen, sich geschickt in dieser neuen Welt zu bewegen. Auch ich habe mir viele Fertigkeiten aneignen müssen. Die Kommunikation über unseren Instant Messaging Dienst IBM Sametime zum Beispiel ist oftmals viel effizienter als Mailen. Die Anzahl meiner Emails hat sich um etwa 60 Prozent reduziert, ich nutze sie fast nur noch für Formales wie offizielle Anweisungen. Aber wichtiger als der Umgang mit den Tools ist die Akzeptanz neuer Rolleneigenschaften: Die Führungskraft ist heute eher Moderator als Boss, also einer, der Entwicklungen einschätzen kann, auch wenn andere im Detail, gerade in Sachen digitaler Zusammenarbeit, vielleicht mehr wissen. Die inhaltliche Führung wird sich von formaler Führung abkoppeln. Das bedeutet aber nicht, dass die Führungskraft am Ende ihre Verantwortung weg-delegieren kann. Und deswegen hat sie natürlich auch nach wie vor das Recht auf souveränes Entscheiden.

XPages: Disable iOS “autocorrect” and “autocapitalize” features

31. Oktober 2012 Posted by Sven Hasselbach

Two of the worst features ever implemented are the “autocapitalize” and “autocorrect” features from iOS devices, because they are enabled by default. It does not make fun to write a blog comment in another language… Or if you just want to enter your email address in a form: The first letter is always upper case, and you always have to kill the suggestion while writing some text.

To disable this behaviour, you have to add two attributes to the components, autocorrect=off and autocapitalize=off:

<xp:inputText id="inputText1">
   <xp:this.attrs>
      <xp:attr name="autocorrect" value="off" />
      <xp:attr name="autocapitalize" value="off" />
   </xp:this.attrs>
</xp:inputText>

To make life a little bit easier you can add these attributes by using a theme, and can disable the theme for every component for which this feature should be enabled:

<theme extends="webstandard"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:noNamespaceSchemaLocation="platform:/plugin/com.ibm.designer.domino.stylekits/schema/stylekit.xsd">

   <control>
      <name>InputField.EditBox</name>
      <property>
         <name>attrs</name>
         <complex type="xp_attr">
            <property>
               <name>name</name>
               <value>autocapitalize</value>
            </property>
            <property>
               <name>value</name>
               <value>off</value>
            </property>
         </complex>
         <complex type="xp_attr">
            <property>
               <name>name</name>
               <value>autocorrect</value>
            </property>
            <property>
               <name>value</name>
               <value>off</value>
            </property>
         </complex>
      </property>
   </control>
   <control>
      <name>InputField.TextArea</name>
      <property>
         <name>attrs</name>
         <complex type="xp_attr">
            <property>
               <name>name</name>
               <value>autocapitalize</value>
            </property>
            <property>
               <name>value</name>
               <value>off</value>
            </property>
         </complex>
         <complex type="xp_attr">
            <property>
               <name>name</name>
               <value>autocorrect</value>
            </property>
            <property>
               <name>value</name>
               <value>off</value>
            </property>
         </complex>
      </property>
   </control>

</theme>

So your users are able to use your application again  – without hating every keystroke. Thank you, Apple!

XPages: Capture Signatures with the jQuery-Plugin ‘jSignature’

26. September 2012 Posted by Sven Hasselbach

In one of my current projects it is one of the goals that the members of the field staff have the possibility to sign a report directly on their iPad. After some research I found the very cool jQuery plugin jSignature, which easily allows to add a signature capture field to a XPage.

The plugin is very easy to use: Just add a <div> to your XPage and initialize the plugin with the .jSignature() constructor and that’s it! The API is very simple, but provides everything needed: The captured signatures can be stored in different formats like PNG or SVG or as native Vector. Additionally they can be encoded as BASE64 or BASE30. The data can restored as easy as they can be saved: Just one API call and it’s done.

To save the signatures to a Notes document, the resulting data can be copied to a hidden input field. In the provied example above I additionally added the format of the signature. For a better handling of the generated data I decided to store the data in the SVG format, because this allows to save it directly in standard notes text field (without having problems because of the 32 K limit). This works well and the data can be displayed in standard browsers without any problems. Only in XPiNC this will not work, because the SVG format is not supported. PDFs doesn’t support SVG too, that’s why I created a converted agent using the Apache Batik framework.

I will add a demo database asap. A description of parameters to customize the plugin can be found here.

P.S. The compressed Version is not running on teamstudio Unplugged. You have to use the uncompressed Version. The example uses the x$ function of Mark Roden.

 XPage example

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

    <xp:this.resources>
        <xp:script src="/x$.js" clientSide="true"></xp:script>
        <xp:script src="/jSignatureHandler.js" clientSide="true" />
        <xp:styleSheet href="/jSignature.css"></xp:styleSheet>
    </xp:this.resources>
    <xp:this.data>
        <xp:dominoDocument var="documentSig" 
           formName="frmSignature" />
    </xp:this.data>

    <script src="js/jquery-1.8.1.min.js" />
    <script src="js/jSignature/jSignature.min.js" />

    <xp:div id="jSignature" style="width:400.0px"></xp:div>
    <xp:scriptBlock id="scriptBlockSignature">
        <xp:this.value><![CDATA[
        $(document).ready(function() { 
            sigHandler.init( "#{id:jSignature}", 
               "#{id:inputHiddenSignatureData}", 
               "#{id:inputHiddenSignatureFormat}" );
        }
        )]]></xp:this.value>
    </xp:scriptBlock>

    <xp:button id="button1" value="Reset">
        <xp:eventHandler event="onclick" submit="false">
            <xp:this.script><![CDATA[
                sigHandler.reset();
            ]]></xp:this.script>
        </xp:eventHandler>
    </xp:button>

    <xp:button id="button2" value="Save">
        <xp:eventHandler event="onclick" submit="true"
           refreshMode="complete">
            <xp:this.script><![CDATA[ 
                return sigHandler.save();
            ]]></xp:this.script>
            <xp:this.action>
                <xp:saveDocument var="documentSig" />
            </xp:this.action>
        </xp:eventHandler>
    </xp:button>

    <xp:inputHidden id="inputHiddenSignatureData" 
       value="#{documentSignature.SignatureData}" />
    <xp:inputHidden id="inputHiddenSignatureFormat"
       value="#{documentSignature.SignatureFormat}" />

</xp:view>

CSJS library “jSignatureHandler.js”

/**
 * signatureHandler
 * JS Object for handling the signature data
 * Requires jSignature jQuery plugin & special 
 * function "x$" from Mark Roden
 * 
 * @author Sven Hasselbach
 * @category JavaScript
 * @category jQuery
 * @category UI
 * @version 1.0
 */
var signatureHandler = function() {
    _module = "signatureHandler";

    _idJSignature: null; // DOM id of JSignatue DIV
    _idItemData: null; // DOM id of INPUT for signature data
    _idItemFormat: null; // DOM id of INPUT for signature format
    _objDOMJSignature: null; // handle to DOM object
    _objDOMItemData: null; // handle to DOM object
    _objDOMItemFormat: null; // handle to DOM object
    _maxSize = 32000; // max characters to store (32K limit!)
    _format = "svg"; // format used

    /**
     * set DOM id of JSignature DIV
     * @param String id
     */
    this.setJSignatureId = function( id ){
        this._idJSignature = id;
    }
    /**
     * get DOM id of JSignature DIV
     * @return String id
     */
    this.getJSignatureId = function(){
        return this._idJSignature;
    }
    /**
     * set DOM id of data item
     * @param String id
     */
    this.setItemDataId = function( id ){
        this._idItemData = id;
    } 
    /**
     * get DOM id of data item
     * @return String id
     */
    this.getItemDataId = function(){
        return this._idItemData;
    }
    /**
     * set DOM id of format item
     * @param String id
     */
    this.setItemFormatId = function( id ){
        this._idItemFormat = id;
    } 
    /**
     * get DOM id of format item
     * @return String id
     */
    this.getItemFormatId = function(){
        return this._idItemFormat;
    }

    /**
     * get handle to DOM object of JSignature DIV
     * @return Object
     */
    this.getJSignatureDOMObj = function(){
        return this._objDOMJSignature;
    }
    /**
     * set handle to DOM object of JSignature DIV
     * @param Object
     */
    this.setJSignatureDOMObj = function( obj ){
        this._objDOMSignature = obj;
    }
    /**
     * get handle to DOM object of data item
     * @return Object
     */
    this.getItemDataDOMObj = function(){
        return this._objDOMItemData;
    }
    /**
     * set handle to DOM object of data item
     * @param Object
     */
    this.setItemDataDOMObj = function( obj ){
        this._objDOMItemData = obj;
    }
    /**
     * get handle to DOM object of format item
     * @return Object
     */
    this.getItemFormatDOMObj = function(){
        return this._objDOMItemFormat;
    }
    /**
     * set handle to DOM object of format item
     * @param Object
     */
    this.setItemFormatDOMObj = function( obj ){
        this._objDOMItemFormat = obj;
    }

    /**
     * initialize object
     * 
     * @param String id of jSignature DIV
     * @param String id of data item INPUT
     * @param String id of format item INPUT
     * 
     */
    this.init = function( idJSig, idItemData, idItemFormat ){
        try{

            // init jSignature
            this._idJSignature = idJSig;
            this._objDOMSignature = x$( this._idJSignature ) ;
            this._objDOMSignature.jSignature();
            // init data item
            this._idItemData = idItemData;
            this._objDOMItemData = x$( this._idItemData );

            // init format item
            this._idItemFormat = idItemFormat;
            this._objDOMItemFormat = x$( this._idItemFormat );

            return true;
        }catch(e){
            var errMsg = _module + "::" + arguments.callee.name + "\n";
            for( p in e ){
                errMsg += p + ": '"  + e[p] + "'\n";
            }
            console.error( "Error!\n\n" + errMsg );
            return false;
        }
    }

    /**
     * reset jSignature
     */
    this.reset = function(){
        try{
            this._objDOMSignature.jSignature("reset");
            return true;
        }catch(e){
            var errMsg = _module + "::" + arguments.callee.name + "\n";
            for( p in e ){
                errMsg += p + ": '"  + e[p] + "'\n";
            }
            console.error( "Error!\n\n" + errMsg );
            return false;
        }
    }
    /**
     * saves the data from jSignature
     * 
     */
    this.save = function(){
        try{
            var datapair =  this._objDOMSignature.jSignature( "getData", _format );
            var format = "data:" + datapair[0];
            var data = datapair[1];
            // check max size!
            if( data.length >  _maxSize){
                alert( "The size of the signature is too large. Please retry!" );
                return false;
            }

            this._objDOMItemData.val( data );

            this._objDOMItemFormat.val( format )
            return true;

        }catch(e){
            var errMsg = _module + "::" + arguments.callee.name + "\n";
            for( p in e ){
                errMsg += p + ": '"  + e[p] + "'\n";
            }
            console.error( "Error!\n\n" + errMsg );
            return false;
        }
    }
}

// init JS instance
sigHandler = new signatureHandler();

XPages: High Performance Applications

18. September 2012 Posted by Sven Hasselbach

During the last months I worked on a high performance XPages application used by a lot of end users.  To get a better data throughput, I decided to use a reverse proxy for load balancing, caching of ressources, SSL connections etc.

For static resources I am using some “special” domains: This means that the browser is allowed to do more HTTP requests at once to the same NSF. If you have for example some images in your database which are reachable from outside via http://www.example.com/mydb.nsf/image.gif, this can be changed to http://static.example.com/mydb.nsf/image.gif (Here you can find a list of best practices).

I solved the problem of multiple execution during the JSF lifecycle  by checking if the request has already actual data (details can be found here – German only), but there was still a problem: Everytime a XPage wants some data to display, a query is sent to the domino server. This is nice if your application requires data in real-time, but not for a normal application – it kills the user experience.

This is why I searched a way which easily allows to implement memory cached database queries for domino databases.  The main idea is that the database query is no longer send directly to the backend . Instead, the request is made against a JSON wrapper, and the request to this wrapper (an agent residing in the same NSF) is done via a proxy. This allows a full control of the requested data.

The browser sends the HTTP request to the XPage and receives the response. The XPage queries the MemCache for data; if the data is not in the cache, the proxy queries the data storage (the domino database) and caches the result. The XPage has to parse the JSON data only, and this boosts the performance in my test environment for about 250-300%.

By “stealing” the session cookie, the database query to the backend database will be done in the context of the user; the security for domino databases is not influenced.

In my solution, I am using Apache 2.2 as reverse proxy. The following modules are additionally enabled for this solution:

  • cache_module
  • deflate_module
  • expires_module
  • headers_module
  • mem_cache_module
  • proxy_module
  • proxy_http_module
  • rewrite_module
  • setenvif_module

The virtual host configuration looks like this:

<VirtualHost *:8080>
 ServerName localhost

 # Enable reverseproxy
 ProxyRequests Off
 ProxyPreserveHost On
 <Proxy *>
  AddDefaultCharset off
  Order allow,deny
  Allow from all
 </Proxy>

 # Proxy config for Domino server
 # 
 ProxyPass / http://localhost:80/
 ProxyPassReverse / http://localhost:80/

  # prevent max-age calculation from Last-Modified
  # prevent If-Modified-Since requests
  # reduces the number of requests that hit the server
 <LocationMatch "/.*$">
  Header unset Last-Modified
  Header unset ETag
  Header unset HTTP_CACHE_CONTROL
 </LocationMatch>

 # MemCache Config
 # 
 CacheEnable mem /
 CacheEnable mem http://

 # Cache-Size 80 MB
 MCacheSize 81920
 MCacheMaxObjectCount 8192

 # Min Obj. Size 1 Byte
 MCacheMinObjectSize 1

 # Max Obj. Size 1 MB
 MCacheMaxObjectSize 1000000

 # cache for 60 seconds by default
 CacheDefaultExpire 60

 # FORCE caching for all documents (without Cache-Control: no-cache)
 CacheIgnoreNoLastMod On

 # force caching for all requests
 # ignore client side Cache-Control header
 CacheIgnoreCacheControl On
 # don't add Set-Cookie header to cache
 CacheIgnoreHeaders Set-Cookie

 # Add expires headers for images, css & js files
 # reduces the number of requests that hit the server
 ExpiresActive On
 ExpiresByType domino/json A600

</VirtualHost>

As you can see, the proxy runs on port 8080, and I have added a special content type “domino/json“. This makes it easier to identify the relevant data.

This is the XPage the user accesses:

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

    <xp:pager layout="Previous Group Next" partialRefresh="true"
        id="pager1" for="repeat1">
    </xp:pager>

    <xp:inputText id="inputSearch" value="#{sessionScope.searchFor}" />

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

    <xp:repeat id="repeat1" rows="30" var="rowData">
        <xp:this.value><![CDATA[#{javascript:
            importPackage( ch.hasselba.xpages.util );

            var sessionId = null;
            try{
                 sessionId = cookie.get("DomAuthSessId").getValue();
            }catch(e){}

            var url = "http://localhost:8080/Data.nsf/DoSearch?OpenAgent";
            url += "&sessionId=" + sessionId;

            if( sessionScope.get("searchFor") !== null ){
                if( sessionScope.get("searchFor") !== "" )
                    url += "&search="; 
                    url += java.net.URLEncoder.encode(sessionScope.get("searchFor"),"UTF-8");
            }

            var data = ch.hasselba.xpages.util.URLReader.read( url, sessionId );
            var parsed = null;
            try{
                 parsed = fromJson(data).data;
            }catch(e){}
            parsed
            }]]>
        </xp:this.value>
        <xp:text escape="true" id="computedField1" value="#{javascript:rowData.LastName}">
        </xp:text>
        &#160;
        <xp:text escape="true" id="computedField2" value="#{javascript:rowData.FirstName}">
        </xp:text>
        <xp:br />
    </xp:repeat>

</xp:view>

The Java class used is really simple, I know there are better ways to do a Http request, but this is a proof of concept.

package ch.hasselba.xpages.util;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;

/**
 * URLReader
 * performs a HTTP request
 * 
 * @author Sven Hasselbach
 * @category URL
 * @category Proxy
 * @version 0.2
 */
public class URLReader {

    // Constants
    private final static String PROPERTY_COOKIE_NAME = "Cookie";
    private final static String PROPERTY_DOMAUTHSESSID_VALUE = "DomAuthSessId=";

    /**
     * reads data from a given URL
     * 
     * @param pURL URL to load data from
     * @param pSessionId session data for doing a request in the current user context
     * @return String containg the result of the http request
     * @author Sven Hasselbach
     * @category URL
     * @category Proxy
     * @version 0.2
     */
    public static String read( final String pURL, final String pSessionId ){
        String data = null;

        try{
            // init the URL connection
            URL url = new URL( pURL );
            URLConnection uc = url.openConnection();

            // "steal" the original user session cookie
            if( !("".equals(pSessionId)))
                    uc.setRequestProperty ( PROPERTY_COOKIE_NAME ,
                       PROPERTY_DOMAUTHSESSID_VALUE + pSessionId);

            // do the HTTP request
            BufferedReader in = new BufferedReader( 
               new InputStreamReader( uc.getInputStream() ));

            // process the data returned 
            StringBuffer strBuf = new StringBuffer();
            String tmpStr = "";
            while((tmpStr = in.readLine()) != null ){
                strBuf.append( tmpStr );
            }
            data = strBuf.toString();

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

        return data;
    }
}

And here comes the JSON handler, a simple Lotus Script agent:

Sub Initialize
    Dim session As New NotesSession
    Dim db As NotesDatabase
    Dim dc As NotesDocumentCollection
    Dim doc As NotesDocument
    Dim isFirst As Boolean
    Dim contextDoc As NotesDocument
    Dim hlp
    Set contextDoc = session.Documentcontext
    Set db = session.Currentdatabase

    ' get the search string from the URL or use the default search
    hlp = Split( contextDoc.QUERY_STRING_DECODED(0), "search=" )
    If UBound( hlp ) = 0 Then
        Set dc = db.Ftsearch("[FirstNAME] CONTAINS AARON", 0)
    Else
        Set dc = db.Ftsearch(hlp(1), 0)
    End If

    ' create the JSON output    
    isFirst = true
    Set doc = dc.Getfirstdocument()

    ' special content type domino/json
    Print |Content-type: domino/json|
    Print
    Print |{"data":[|
    While Not doc Is Nothing
        If Not isFirst Then Print ","
        Print |{"LastName":"| & doc.LastName(0) & _
        |","FirstName":"| & doc.FirstName(0) & |"}|
        isFirst = False
        Set doc = dc.Getnextdocument( doc )
    Wend

    Print |]}|

End Sub

In the next days I will provide a sample database and add more details. A database with 300k test datasets will be added too.