Posts Tagged: ‘ServerSide JavaScript’

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.

XPiNC and Attachments: A better PDF handling

15. November 2012 Posted by Sven Hasselbach

Quick-n-Dirty: Disable Validation for FileDownload control

24. Oktober 2012 Posted by Sven Hasselbach

If you are using the file download control,  you sooner or later want to allow your users to delete an existing file. But if you have some required fields on your XPage, a deletion is not possible because the validation is always fired before the attachment is removed.

To disable to validation you can use this little SSJS snippet which sets the “disableValidators” property for all events of the UIFileDownload control. Just add the following code to the beforeRenderResponse event of your XPage and change the id of the component to fit your requirements.

<xp:this.beforeRenderResponse>
   <![CDATA[#{javascript:
      /***
       * disable validation for UIFileDownload control
       * 
       * @param UIFileDownload component
       * @author Sven Hasselbach
       * @category SSJS
       * @category UI
       * @version 0.2
       */
      function disableFileDownloadValidation( fDownload:com.ibm.xsp.component.UIFileDownload ){
         if( fDownload === null )
            return;

         rekDisableFileDownloadValidation( fDownload );
      }

      function rekDisableFileDownloadValidation( component:javax.faces.component.UIOutput ){
         try{
            var children:java.util.List = component.getChildren();
            var it:java.util.Iterator = children.iterator();
            var curChild:javax.faces.component.UIOutput;

            while( it.hasNext() ){
               curChild = it.next();
               if( typeof( curChild ) === 'com.ibm.xsp.component.xp.XspEventHandler' )
                  curChild.setDisableValidators( true );

               rekDisableFileDownloadValidation( curChild );
            }
         }catch(e){}    
      }

      disableFileDownloadValidation( getComponent( 'fileDownload1' ) );
   }]]>
</xp:this.beforeRenderResponse>

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

Quick-n-Dirty: Use your own Factory classes in XPages

18. Oktober 2012 Posted by Sven Hasselbach

Here is a easy way to use your own factory classes in XPages:

1. Create a file named “com.ibm.xsp.factories.properties” in the WEB-INF-Folder of your NSF

 

 

2. In this file, define the factory classes you want to use in the format<NAME>=<Java Class>

HelloWorldFactory=ch.hasselba.factory.HelloWorld

3. Create a java class, in this example ch.hasselba.factory.HelloWorld

package ch.hasselba.factory;

public class HelloWorld 
{
   public HelloWorld(){
      System.out.println("Hello World Factory alive!");
   }

   public String getMessage(){
      return "Hello World!";
   }

}

The factory classes you are adding require a constructor. They will be instantiated during runtime.

4. After saving your java class and opening a XPage you should see the message on the server console (perhaps you have to do a clean first).

This means that the Factory is now accessible and is ready to use. The class will only be instantiated once during runtime.

5. To use your Factory, you have to do a lookup first, before you can use it. This can be done using the com.ibm.xsp.factory.FactoryLookup class which can be accessed via the current application (Please read the notice in the ExtAPI documentation!).

Here is a SSJS code snippet:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
   <xp:label id="labelHelloWorld">
      <xp:this.value>
         <![CDATA[#{javascript:
            var app = facesContext.getApplication();
            var facLookUp = app.getFactoryLookup();
            var hwFac = facLookUp.getFactory("HelloWorldFactory");
            hwFac.getMessage();
         }]]>
      </xp:this.value>
   </xp:label>
</xp:view>

The main difference between a managed bean and this approach is, that the class is instantiated as soon the JSF application starts running, not if you access the bean in your code for the first time. The factory class can do what you want, f.e. run a thread or whatever. The result of the operations can be accessed via the  factory lookup.

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

Quick-n-Dirty: Control Dojo generation for individual XPages

17. Oktober 2012 Posted by Sven Hasselbach

Another interesting question has been asked on stackoverflow.com: How to enable or disable the Dojo libraries for individual XPages, not the whole application?

Just adding the parameter as shown below to the XPage won’t work:

<xp:this.properties> 
 <xp:parameter name="xsp.client.script.libraries" value="none" />
</xp:this.properties>

The workaround is to add a single line to beforePageLoad or beforeRenderResponse event:

facesContext.getRequestParameters().setJsLibrary(0);

The parameter can be set as the following

  • “0″ disables Dojo Framework
  • “1″ enables Dojo Framwork
  • “2″ enables Dojo Lite Framework

If you are using the ExtLib, there is still a problem with the automatically added CSJS libraries. These headers will be added automatically (it is the same result if you are disabling dojo application wide):

<script type="text/javascript">dojo.registerModulePath('extlib', '/xsp/.ibmxspres/.extlib');</script>
<script type="text/javascript" src="/xsp/.ibmxspres/.mini/dojo/@Eya.js"></script>

Edit:

This can only be removed by disabling the ressource aggregation or disabling the Extension Library for the whole application.

You can disable it f.e. by adding this to your XPage beforeRenderResponse event:

facesContext.getRequestParameters()
   .setProperty("xsp.resources.aggregate", "true")

Quick-n-Dirty: A simple isRecycled() method (2)

11. Oktober 2012 Posted by Sven Hasselbach

Tommy Valand improved the idea: In his solution the isDead method of the NotesBase class is accessed, and this works better, because the method addionally checks for the C object handle.

Here you can find the method: http://stackoverflow.com/questions/12740889/what-is-the-least-expensive-way-to-test-if-a-view-has-been-recycled

Quick-n-Dirty: A simple isRecycled() method

10. Oktober 2012 Posted by Sven Hasselbach

You will find that the domino java API does not have a suitable method to test if a domino object was already recycled or not. And because of the internal caching of some method calls it is not reliable to check for example for a specific property with a try/catch block.

But domino objects have a private property isdeleted which is transient and gives us the required information. With Java Reflection it is possible to access this property:

package ch.hasselba.domino;

import java.lang.reflect.Field;

import lotus.domino.Base;
import lotus.domino.local.NotesBase;

/**
 * DominoUtil a library containing usefull Tools / Methods for domino
 * 
 * @author Sven Hasselbach
 * @category Domino
 * @category Tools
 * @version 1.2
 */
public class DominoUtil {

    private static Field isDeleted;    

    /**
     * checks if a domino object is already recycled
     * 
     * @param lotus.domino.local.NotesBase
     *           obj to check
     * @author Sven Hasselbach
     * @category Domino
     * @category Tools
     * @version 1.1
     */
    public static boolean isRecycled(NotesBase obj) {

        if( isDeleted == null )
            initIsDeleted();

        try {
            return isDeleted.getBoolean(obj);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * checks if a domino object is already recycled
     * 
     * @param lotus.domino.Base
     *           obj to check
     * @author Sven Hasselbach
     * @category Domino
     * @category Tools
     * @version 1.1
     */
    public static boolean isRecycled(Base obj) {
        return isRecycled( (NotesBase) obj );
    }

    /**
     * inits Reflection of isDeleted field
     */
    private static void initIsDeleted(){
        try {
            isDeleted = lotus.domino.local.NotesBase.class
                    .getDeclaredField("isdeleted");
            isDeleted.setAccessible(true);

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

}

Here is a short demonstration of the usage in an agent (it works in XPages / SSJS too):

import lotus.domino.*;
import ch.hasselba.domino.DominoUtil;

public class JavaAgent extends AgentBase {

    public void NotesMain() {

      try {
          Session session = getSession();
          AgentContext agentContext = session.getAgentContext();
          Database db = session.getCurrentDatabase();

          View v1 =  db.getView( "AllDocuments" );
          View v2 =  db.getView( "AllDocuments" );

          System.out.println( "Recycled: " + 
             DominoUtil.isRecycled( v1 ) );

          v2.recycle();

          System.out.println( "Recycled: " + 
            DominoUtil.isRecycled( v1 ) );

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

Teamstudio Unplugged: SSJS & Error Messages

28. September 2012 Posted by Sven Hasselbach

Today I had to fight with a mysterious error message in a XPage application which is running on Teamstudio Unplugged:

SyntaxError: missing ; before statement

This IS the message. No more information. No stack trace. No library name. Nothing!

It was a hard piece of work to find the needle in the haystack: I used the synchronized keyword in a SSJS library. Hope in future Teamstudio will provide better error informations…

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.

XPages: JQuery Gantt Module

16. September 2012 Posted by airwolf89

In the past weeks I was trying something new, at least new for me…

I realised that using jQuery in Xpages is a wonderful idea. Mark Roden has a nice blog with great things you can do with jQuery in XPages. You can find it here: Xomino Also check out his demo site: Xomino JQinX Demo

In one project I wanted to use a gantt chart. I want to write this article to show you how it’s done and warn you about the problems I had.

Let’s start.

To use jQuery you have to download it and integrate it in your database. Build your own version of jQuery here: JQuery Download It is the download page of JQuery UI, a set of many nice plugins and modules for jQuery. A version of the base jQuery is included. You can also configure a custom theme for your jQuery modules. It is on the right menu when you follow the link “design a custom theme

For the gantt chart I am using this module: Taitems JQuery Gantt Chart Module

So, now you have to integrate these files into your database. You can do this either via the resource category in the database, or via the PackageExplorer (it is an Eclipse View) I recommend using the package explorer, because you can manage your files more easily. Simply drag and drop your files and create some folders to organize them.

Now you have to load them into your application. You can do this easily via a theme with the following statement:

<resource>
<content-type>application/x-javascript</content-type>
<href>js/jquery-1.7.min.js</href>
</resource>

Or you load them directly into your code via the resource properties of the XPage/ Custom Control

<xp:script src="js/jquery-ui-1.8.23.custom.min.js" clientSide="true"/>

You have to do this for the jQuery base module, the UI module and the gantt module, also you have to load the CSS files.

First problem I had to face: The gantt module doesn’t work with every jQuery version. I was using jQuery 1.8.0. But when using this module, I had an error message which complained about some undefined variables (TypeError: m is undefined) Using the 1.7 version was a solution to this.

Now we can get started. On the page of the gantt module is an easy manual how to use this module. It is quite self explaining.

To implement a gantt chart, you only have to put in these few lines of code (client side of course)

<xp:scriptBlock id="scriptBlock1">
<xp:this.value>
// + "/ganttJSONBuilder?OpenAgent";
// + "/js/data.js";
jQuery( function() {
var dataPath = location.href.substring(0,
location.href.lastIndexOf('/') + 1)
+ "/ganttJSONBuilder?OpenAgent;

$(".gantt").gantt( {
source : dataPath
});
});]]>
<<!--xp:this.value>
<<!--xp:scriptBlock>

As data source the module uses JSON data. Use this site to validate your JSON data easily. The structure of your data is explained in the manual site of the module.

Be aware, the module expects a URL as data source. That means you can’t use server side Javascript in that scriptblock to generate the data source. So, we have two options on how to load the data into the module, either via a file which you can put into the WebContent folder in your database, or you call an agent. The agent is probably the better option because you sure want to load data dynamically.

Using an agent to achieve this is basically easy, you only have to write your JSON string into a variable and then you do this:

PrintWriter pw = getAgentOutput();
pw.println(output);

The difficulty of generating the JSON string itself depends on your data. In my case it was a simple project management, so some tasks, projects and editors. I used a view. I looped the entries and parsed the information into a JSON String.

There are some nice little things you should know when doing so.

The first line of your JSON string has to be the content type of the output, for JSON it is: Content-type: application/json; charset=utf-8\n
Also, it is very important that you use line breaks in the string, simply by using \n. If you aren’t doing that, your JSON string will not be properly interpreted and the module shows nothing but a nice grey bar.
Easily to overlook, but also important, in JSON you have to use a comma to separate your entries. Be sure you have no comma behind your last entry or parameter. It will result in a misinterpretion of the data.
The next important thing is, in the JSON data, you have to provide all entries defined in the manual. That means, if you forgot to write one of the parameters into the string, it is valid, but not recognized by the gantt module and isn’t doing anything. All of them need a value. providing nothing will have the same result: Nothing =)
I wanted to have my projects and tasks separated. In one line the project and then in the other lines the related tasks. This is possible by writing an extra entry to your JSON string, in which you only write the information about the project. When writing the tasks of the project, you probably don’t want to write the project in front of each task. If you think you can define an empty string for the name parameter of the JSON entry, you are wrong. The module needs a value of at least one character. Using an empty string results in not displaying the chart and some nice error messages. Using a space (” “) is the solution.
Also, you have to provide a start and end date for each entry. I wanted to have no duration for the project, so I didn’t provide the “from” and “to” parameter, it rewarded me with some other javascript errors. So I used the whole duration of the project for the project entry, it’s a good feature anyway.

A nice feature: You can use HTML in your JSON parameters, so, if you use
tags for example in your desc parameter and some formatting tags, you can make the mouseOver of the entries look more beautiful and put some more information into it.

If you think the styleClasses available for your entries in the chart (ganttRed, ganttGreen, ganttOrange) are not that beautiful, or you want to use some more, you can easily define some. There is a css file in the module. Looks like this:

.fn-gantt .ganttOrange {
background-color: #FCD29A;
}
.fn-gantt .ganttOrange .fn-label {
color: #714715 !important;
}

Copy those entries and do your own styles, the class at the end of the definition is recognized by the module, so you can use it directly in the “customClass” parameter of your JSON.

But there is one big problem when using this module. Above the chart, I have some buttons. I wanted to load different charts with those buttons. Easily done I thought, just firing a partial execute on the chart container to set another data source or something. But, nothing happens, in fact really nothing, not even an event which I could see in my FireBug. Some other buttons with only clientside code are working fine. All other buttons on the whole page are working fine, only those I have directly above my chart didn’t wanted to work. I haven’t figured out what’s the problem, if I find it, I will let you know.

Another problem is that the gantt chart is not working in the Notes Client. If I open the Xpage in my Notes client, the chart isn’t loaded, only a nice grey bar. Other jQuery modules are working fine, for example the accordion or flip is working, but not the module. If you want to use it in the NotesClient you should think about another solution… or wait until I find a solution for this problem =)

Another thing you should consider are loading times. The loading time is increasing dramatically proportional on how many entries you display (rowsPerPage parameter in the gantt function) and how many years they cover. If you want to display some tasks from a period of 10 years or more, you have to wait a bit. I tried a period of 30 years… not a good idea, my browser crashed… after about 5 minutes of loading.

So, I think that’s all I know about the module and how to use it. Despite all the small and big problems I have a nice application with some really nice gantt charts. If you are interested in such an application, you should visit this page (Cobalt Software GmbH), the new version should be available soon. Ok, sorry for the advertisement…

If you have any questions or solutions for my unsolved problems, the comment section is all yours =)

Hope it was useful for some of you.


Filed under: Notes & XPages Tagged: Chart, Cobalt Software GmbH, CSS, Datasource, Gantt, Javascript, JQuery, JSON, Mark Roden, Notes, Parameter, Plugin, Serverside Javascript, Taitem, Tools, Xomino, XPages, Xpages in NotesClient, XpInc

XPages: The Outputstream and binary data

12. September 2012 Posted by Sven Hasselbach

If you want to get control over the outputstream of a XPage, you can use the response object from the ExternalContext:

var response = facesContext.getExternalContext().getResponse()

This will give you access to an object of type com.ibm.xsp.webapp.XspHttpServletResponse which allows some basic operations, but the response will always be encoded to UTF-8. You can not return any binary data directly.

But if you access the underlying LCDAdapterHttpServletResponse directly, it is possible to get the full control for the outputstream.

var exCon = facesContext.getExternalContext();
var response = exCon.getResponse().getDelegate();

This allows you to send any data you want to the browser. Here is an example for a JPEG rendered directly in the XPage:

1. The XPage

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

   <xp:this.afterRenderResponse>
      <![CDATA[#{javascript:
         importPackage( ch.hasselba.xpages.demo );
         var exCon = facesContext.getExternalContext();
         var response = .getResponse().getDelegate();
         ch.hasselba.xpages.demo.JPEGGenerator.generate( response );
      }]]>
   </xp:this.afterRenderResponse>

</xp:view>

 

2. The Java object to generate a JPEG

package ch.hasselba.xpages.demo;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.IOException;
import com.sun.image.codec.jpeg.ImageFormatException;
import com.sun.image.codec.jpeg.JPEGCodec;
import java.awt.Color;
import java.awt.Font;

/**
 * JPEG Generator for a simple demonstration of controlling the
 * HttpServletResponse output
 * 
 * @author Sven Hasselbach
 * @category Demo
 * @category Servlet
 * @category Image
 * @version 1.0
 */

public class JPEGGenerator {

    private static final long serialVersionUID = 1L;
    private static final int WIDTH = 800;
    private static final int HEIGHT = 200;
    private static final Color BACKGROUND_COLOR = new Color(224, 224, 224);
    private static final Color COLOR = new Color(0, 0, 0);
    private static final Font FONT = new Font("Times New Roman", Font.BOLD, 46);

    /**
     * generates a JPEG image and sends the result to the outputstream of the
     * HttpServlet
     * 
     * @param response
     *            HttpServletResponse
     * @author Sven Hasselbach
     * @version 1.0
     */
    public static void generate(final HttpServletResponse response) {
        ServletOutputStream out = null;
        try {

            // set the content type for JPEG
            response.setContentType("image/jpg");

            // get the ouput stream
            out = response.getOutputStream();

            // generate a image to convert
            BufferedImage img = createImage();

            // convert image to jpeg and send to output
            JPEGCodec.createJPEGEncoder(out).encode(img);

        } catch (ImageFormatException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    /**
     * creates a simple "Hello World" image
     * 
     * @return BufferedImage
     * @author Sven Hasselbach
     * @version 1.0
     */
    public static BufferedImage createImage() {
        BufferedImage image = new BufferedImage(WIDTH, HEIGHT,
                BufferedImage.TYPE_BYTE_INDEXED);
        Graphics graphics = image.getGraphics();
        graphics.setColor(BACKGROUND_COLOR);
        graphics.fillRect(0, 0, image.getWidth(), image.getHeight());
        graphics.setColor(COLOR);
        graphics.setFont(FONT);
        graphics.drawString("Hello World!", 10, HEIGHT / 2);

        return image;
    }

}

There is no need for splitting this example in a separated Java class. You can directly access the output in SSJS and send your binary data directly via write method of the ServletOutputStream.

SSJS: Execute remote SSJS Code

24. August 2012 Posted by Sven Hasselbach

I have created a small helper class to run SSJS code from a remote server. The basic idea behind this class is a question on stackoverflow: http://stackoverflow.com/questions/12054733/include-jss-file-from-notes-document-as-resource

As far as I know there is no way to add a SSJS resource via the src attribute, this won’t work:

<xp:this.resources>
   <xp:script src="http://localhost:8080/test.jss"
      clientSide="false" />
</xp:this.resources>

It will always fail, even if the file is available, has the correct file extension etc.

That’s why I wrote the code, it’s only a proof of concept. There are no security features to protect against manipulations, no caching for better performance and whatever.

Here is a demo XPage to demonstrate how to use it:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
   <xp:label id="label1">
      <xp:this.value>
         <![CDATA[#{javascript:
            importPackage( ch.hasselba.xpages.util.ssjs );
            SSJSUtil.executeSSJSFromURL("http://localhost:8080/test.jss");
            test();
         }]]>
      </xp:this.value>
   </xp:label>
</xp:view>

The method executeSSJSFromURL loads a text file from the given URL, creates a method binding with the content and invokes it. Then, the SSJS code is executed directly – all functions, objects and variables defined in the remote code are ready to use from now on. As you can see above, the method test() is called which is defined in the remote SSJS file.

And here is the Java code:

package ch.hasselba.xpages.util.ssjs;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import com.ibm.xsp.util.StreamUtil;
import com.ibm.xsp.page.compiled.ExpressionEvaluatorImpl;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;

/**
 * SSJSUtil
 * 
 * helper class for SSJS operations
 * 
 * @author Sven Hasselbach
 * @version 1.0.2
 * @category SSJS
 * @category Utility
 */

public class SSJSUtil {

    private static final String NEWLINE = "\n";
    private static final String SSJS_EXPRESSION_BEGIN = "#{javascript:";
    private static final String SSJS_EXPRESSION_END = "}";

    /**
     * Loads SSJS code from a given URL and executes it
     * Declared methods and objects are reachable for other SSJS code
     * 
     * @param url of the SSJS code
     * @return Object resulting object from SSJS execution
     * @author Sven Hasselbach
     * @version 1.0.1
     * @category Utility
     */
    public static Object executeSSJSFromURL( final String url ){
        return execute( loadFromURL( url ) );
    }

    /**
     * loads a URL stream and converts it to a string
     * @param url of the resource
     * @return String containing the data loaded from given url
     * @author Sven Hasselbach
     * @version 1.0.1
     * @category Utility
     */
    public static String loadFromURL( final String url ){
        String ret = null;
        try{
            FacesContext fc = FacesContext.getCurrentInstance();
            InputStream in = StreamUtil.getInputStream(fc, url);
            ret = inputStreamToString( in );
        }catch(Exception e){
            e.printStackTrace();
        }
        return ret;
    }

    /**
     * executes given SSJS code and returns the result (if any)
     * functions / libraries are added to runtime 
     * 
     * @param ssjsCode code to execute
     * @return resulting object
     * @author Sven Hasselbach
     * @version 1.0.2
     * @category SSJS
     */
    public static Object execute( final String ssjsCode ){
        Object ret = null;

        try{
            String valueExpr = SSJS_EXPRESSION_BEGIN + ssjsCode + SSJS_EXPRESSION_END;
            FacesContext fc = FacesContext.getCurrentInstance();
            ExpressionEvaluatorImpl evaluator = new ExpressionEvaluatorImpl( fc );
            ValueBinding vb = evaluator.createValueBinding( fc.getViewRoot(), valueExpr, null, null);
            ret = vb.getValue(fc);
        }catch(Exception e){
            e.printStackTrace();
        }
        return ret;
    }

    /**
     * converts the data from a given inputstream to a string
     * 
     * @param in InputStream to convert
     * @return String containing data from input stream
     * @throws IOException
     * @author Sven Hasselbach
     * @version 1.0.1
     * @category Utility
     */
    public static String inputStreamToString(final InputStream inStream) throws IOException {
        BufferedReader bufReader = new BufferedReader( new InputStreamReader(inStream) );
        StringBuilder strBuilder = new StringBuilder();
        String line = null;

        while ((line = bufReader.readLine()) != null) {
            strBuilder.append(line);
            strBuilder.append( NEWLINE );
        }
        bufReader.close();

        return strBuilder.toString();
     }
}

By the way: You cannot use the import method in the remote code.

SSJS: What’s “this”?

24. August 2012 Posted by Sven Hasselbach

In Serverside JavaScript the keyword this always refers to the “owner” of the function which is executing,  or rather, to the object that a function is a method of.

This means f.e. that this refers to the UIComponent which contains the SSJS code. If you add a label to a XPage and compute the value…

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
   <xp:label id="label1">
      <xp:this.value>
         <![CDATA[#{javascript:return this;}]]>
      </xp:this.value>
   </xp:label>
</xp:view>

this will always return the current corresponding instance of the XspOutputLabel:

But if you are inside a function this will return the current JavaScript object instead:

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

   <xp:label id="label1">
      <xp:this.value>
         <![CDATA[#{javascript:
            var arr = [1, 2];

            function callMe(){
               var obj = this;
               for( var p in obj ){
                  print( p + " -> " + obj[p] + " [" + typeof(obj[p]) + "]" );
               }    
            }
            callMe()
         }]]>
      </xp:this.value>
   </xp:label>

</xp:view>

The result on the console looks like this:

If you are using call or apply, you can overwrite the this object:

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

   <xp:label id="label1">
      <xp:this.value>
         <![CDATA[#{javascript:
            function callMe(){
               println(this);

               for( p in arguments ){
                  println( arguments[p] );
               } 
            }

            callMe.call("1","2","3");
         }]]>
      </xp:this.value>
   </xp:label>
</xp:view>

Now this is set to “1“. The arguments are “2” and “3“, as you can see on the server console:

Quick-n-Dirty: @ClientType() in XPages

23. August 2012 Posted by Sven Hasselbach

The @ClientType formula provides an interesting behaviour: If you add the value to a label, the result will be as expected. It returns “Web” in Browser and returns “Notes” in XPiNC.

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
   <xp:label value="#{javascript:@ClientType()}" id="label1" />
</xp:view>

But if you add it to a computed field on a form and use this form in a datasource, the result in XPiNC is not “Notes“, it is “None” instead.

<?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="Test" 
         computeWithForm="onload">
      </xp:dominoDocument>
   </xp:this.data>

   <xp:label value="#{javascript:@ClientType()}" id="label1" />
   <xp:br />
   <xp:label id="label2">
      <xp:this.value>
         <![CDATA[#{javascript:
            document1.getItemValueString("ClientType")
         }]]>
     </xp:this.value>
   </xp:label>
</xp:view>

[The computeWithForm property of the datasource has to be set to "onload" or "onsave". If set to "onboth" this will not work.]

This behaviour gives you control about the subforms which are used in the form, depending of the client and/or technology. Just add a computed subform to your form and add a @Formula like this:

@If(
    @ClientType="Web";"WebAccess";
    @ClientType="Notes";"NotesAccess";
    "XPiNCAccess"
)

This allows you to add specific fields to a document, or can help to identify the way how the document was created. If you add a field in the querySave event on the XPage, you can identify if this document was created in the web by XPage or old-school web access of the form.

<xp:this.data>
   <xp:dominoDocument var="document1" formName="Test"
      computeWithForm="onsave">
      <xp:this.querySaveDocument>
         <![CDATA[#{javascript:
            document1.replaceItemValue("ClientTypeXPage", "1"))
         }]]>
      </xp:this.querySaveDocument>
   </xp:dominoDocument>
</xp:this.data>

By extending the subform computation, you can use a subform for every type of access:

@If(
    @ClientType="Web" & ClientTypeXPage="1";"XPageWebAccess";
    @ClientType="Web";"WebAccess";
    @ClientType="Notes";"NotesAccess";
    "XPiNCAccess"
)

XPages: Access a datasource from one custom control in another one

5. August 2012 Posted by Sven Hasselbach

On stackoverflow.com, a very interesting question was asked: How can you access a document datasource from one custom control in another custom control. And here comes my solution in a small SSJS function.

First you have to add an id to the custom control which contains the datasource. This gives you a handle for other custom controls:

<xc:ccDS1 id="idDSComponent"></xc:ccDS1>

Now it is possible to get the custom control by using the SSJS function getComponent(). The datasources are stored in the data attribute of the custom control. You just have to iterate through them and check every entry for the name you are looking for.

And here is the code snippet to access the datasource in another custom control:

/***
 * getDatasource
 * Resolves a datasource from a custom control
 * 
 * @param componentId    id of the component containing the datasource
 * @param dsName    name of the datasource in the component
 * @author Sven Hasselbach
 */
 function getDatasource( componentId:String, dataSourceName:String ):com.ibm.xsp.model.domino.DominoDocumentData {
    try{
       var data:java.util.ArrayList = getComponent( componentId ).getAttributes().get("data");
       if( data == null )
          return null;
                    
       var it:java.util.Iterator = data.iterator();
       var obj = null;
       while( it.hasNext() ){
          obj = it.next();
          if( obj.getVar() == dataSourceName )
             return obj;
       }
    }catch(e){
       print( e );
    }
}

To use the function you have to use it like this:

getDatasource( "idDSComponent", "document1" )

Here is the link to the original question: How to get document datasource in another Custom Control?