11 Oct 2014

Quick Tip: Using working sets in Domino Designer and Eclipse


I use working sets in Domino Designer all the time otherwise the Application Navigator becomes unbearable.You can also use working sets in the Package Explorer but unfortunately as standard they are not linked to the working set selected in the Application Navigator. Brad Balassaitis has previously blogged how to configure the Package Explorer and Application Navigator to use the same working set: XPages Tip: Select a Working Set in Package Explorer (and Navigator)

As I transition into doing more Java work in Eclipse I decided to use working sets there too. By default the Package Explorer in Eclipse does not display your projects grouped by their working sets although you can add new and existing projects into them.
To display working sets in the package explorer click the 'View Menu' button in the top right, then select 'Top Level Elements' and 'Working Sets'

I also find it very useful that you can add a project to multiple working sets.

If like myself you use working sets heavily this post from Paul Withers on Restoring Domino Designer…Including Working Sets will be very useful.

Anybody have any useful tips when using Eclipse for XPages OSGI Development?

2 Oct 2014

Domino OSGI (Part 4) - Deploy a custom plugin to Domino Designer

In my previous post I walked through creating a simple layout control as an OSGI plugin.
The next step is to deploy the control to designer this is where the set up we did in part 2 comes in.

Inside eclipse then right click the plugin then go to Export
Select 'Deployable plug-ins and fragments' and click next.
Ensure your plug-in is ticked then set the destination as 'Directory' and set that to be your newly created plugin directory, finally click finish.

Your plugin should now be available inside Domino Designer.
Inside Designer create a new XPage, now inside your controls palette you should see new category 'JMC Layout Controls'.

The category is based on what was set in the xsp-config file:
15
16
17
18
   <designer-extension>
          <in-palette>true</in-palette>
          <category>JMC Layout Controls</category> 
... 

Just drag the control on to your XPage as you would any other control and hey presto:

26 Sept 2014

Domino OSGI (Part 3) - Creating a simple OSGI Plugin


Recently I have had a gentle nudge from a couple of people to continue my blog posts, as everyone can appreciate thinks occasionally fall be the wayside especially when children are about.

In my first post I discussed how to set up the eclipse environment for creating Domino OSGI Plugins and in the second how to configure Domino Designer ready install your plugins.

In this post I'm going to walk through creating a very simple plugin, a standard page banner/header.

Firstly you will need to create a new project:
File -> New -> Plug-in Project
Enter a project name, change the location if you wish and add to a working set if you like (I always do).
Click Next
Change the name if you wish and un-tick Generate an Activator and click Next
Un-tick Create a plug-in using one of the templates
You should now have a project open in Eclipse.
To create a component you will need use some of the built-in Domino libraries therefore we will need to add them as dependencies to the project.
To import the required libraries open the Manifest.MF file, open the dependencies tab and click add
Type com.ibm.commons into the text box then select the plug-in from the list and click OK
Now select the newly added plugin and click properties
Delete the minimum version and click OK
Now do the same again for 'com.ibm.xsp.core'

The next step will be to create some classes, one for the component itself, one for the renderer and one for the library extension point. We will also need to create some of config files.

To create the component class right click the src folder and click New -> Class
In the new class window enter a package name, name for the class and add a superclass 'javax.faces.component.UIOutput'
Click Finish

In your newly create class you will need to set a renderer and a family, in this example I have created static variables then set the renderer type in the constructor and and overridden the getFamily method.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class HeaderComponent extends UIOutput {
 private static final String DEFAULT_RENDERER_TYPE =
  "com.jmc.example.layout.header";
 private static final String DEFAULT_FAMILY =
  "com.jmc.example.layout";
 
 public HeaderComponent(){
  setRendererType(DEFAULT_RENDERER_TYPE);
 }
 
 @Override
 public String getFamily() {
  return DEFAULT_FAMILY;
  
 }
}  

Next we will need to create a renderer class.
In the new class window enter a package name, name for the class and add a superclass 'com.ibm.xsp.renderkit.FacesRenderer' and click Finish

This new renderer class will be used to output the HTML markup for the component
In the example I generate the markup in the encodeEnd method using a response writer.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.jmc.xsp.example.layout.renderer;

import java.io.IOException;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;

import com.ibm.xsp.renderkit.FacesRenderer;
import com.jmc.xsp.example.layout.component.HeaderComponent;

public class HeaderRenderer extends FacesRenderer {
 
 public void encodeBegin(FacesContext context, UIComponent component) throws IOException {
  // nothing needed here as all rendering being done in encodeEnd
 }

 public void encodeEnd(FacesContext context, UIComponent component) throws IOException {
  
  //check the component is the header component as expected
  if (!(component instanceof HeaderComponent)) {
   throw new IOException(
     "HeaderRenderer must be used with an instance of the Header Component");
  }
  
  HeaderComponent header = (HeaderComponent) component;
  ResponseWriter writer = context.getResponseWriter();
  
  // Write the header tag
  writer.startElement("header", header);

  // Add some content
  writer.startElement("h1", header);
  writer.write("Header Component");
  writer.endElement("h1");
  
  //close the header tag
  writer.endElement("header");
 }
}

We now need to create the xsp-config and the faces-config files. Some people like to store these files within a META-INF folder in the src folder personally I prefer to have them in the same package structure as the rest of the plugin.
First create a new package in the src folder, I'm going to call it 'com.jmc.xsp.example.layout.config'.
Then you need to create 2 new files inside this package; 'headerComponent.xsp-config' and  'headerComponent-faces-config.xml'.
The xsp-config file is used by Domino Designer to add the control.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<faces-config>
 <faces-config-extension>
  <namespace-uri>http://www.johnmcooper.co.uk/xsp/example</namespace-uri>
  <default-prefix>jmc</default-prefix>
 </faces-config-extension>
 <component>
  <description>Layout - Header section</description>
  <component-type>com.jmc.example.layout.header</component-type>
  <component-class>com.jmc.xsp.example.layout.component.HeaderComponent</component-class>
  
  <component-extension>
   <tag-name>header</tag-name>
   <component-family>com.jmc.example.layout</component-family>
   <renderer-type>com.jmc.example.layout.header</renderer-type>
   <designer-extension>
          <in-palette>true</in-palette>
          <category>JMC Layout Controls</category>
       <render-markup>
        &lt;?xml version="1.0" encoding="UTF-8"?&gt;
        &lt;xp:view xmlns:xp="http://www.ibm.com/xsp/core"&gt;
        &lt;xp:panel &gt;
        &lt;h1&gt;
        Header Component
        &lt;/h1&gt;
        &lt;/xp:panel&gt;
        &lt;/xp:view&gt;
       </render-markup>
         </designer-extension>
  </component-extension>
 </component>
</faces-config>

The faces-config file will be used to map the control to its corresponding renderer. This done by matching the output from the control's getFamily and getRendererType methods to the component-family and renderer-type in the file.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<?xml version="1.0" encoding="UTF-8"?>
<faces-config>
  <render-kit>
    <renderer>
      <component-family>com.jmc.example.layout</component-family>
      <renderer-type>com.jmc.example.layout.header</renderer-type>
      <renderer-class>com.jmc.xsp.example.layout.renderer.HeaderRenderer</renderer-class>
    </renderer>
  </render-kit>
</faces-config>

Finally we need to create a library class which is used when the library is loaded to find the names of the config files. The new class needs to have 'com.ibm.xsp.library.AbstractXspLibrary' as it's superclass.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.jmc.xsp.example.layout.library;

import com.ibm.xsp.library.AbstractXspLibrary;

public class Library extends AbstractXspLibrary {

 public String getLibraryId() {
  return "com.jmc.xsp.example.layout.library";
 }
 public String getPluginId() {
  return "com.jmc.xsp.example.layout";
 }
 @Override
 public String[] getFacesConfigFiles() {
  return new String[]{
    "com/jmc/xsp/example/layout/config//headerComponent-faces-config.xml"
  };
 }
 @Override
 public String[] getXspConfigFiles() {
  return new String[]{
    "com/jmc/xsp/example/layout/config//headerComponent.xsp-config"
  };
 }
}

Now we have the library class we can contribute it as an extension point.
Open the MANIFEST.MF file, go to the Extensions tab, click Add and select 'com.ibm.commons.Extension'. Click Finish
You should now have a new extension listed, click service underneath the new extension then add 'com.ibm.xsp.Library' to the type field, in the class field ad the name of your library class in my case 'com.jmc.xsp.example.layout.library.Library'. Now save your changes.

You should now have an Plugin ready to be deployed to a Domino Server and Domino Designer

I have uploaded the example plugin the Bitbucket: jmc-example-layout

19 May 2014

Domino OSGI (Part 2) - Configuring Eclipse for XPages OSGI Plugins

In my previous post I described how to initially setup eclipse in preparation for creating your own XPage plugins. This will then allow you to deploy your Plugins from eclipse and debug when necessary.

To use your own controls you will need to create a plugin and install this into Domino Designer and onto the Domino Server. My preference is wrap the plugin up into an update site and install as described in this article: How to install the new XPages Extension Library with the Eclipse Update Site database

One of the issues with the above is that if you are constantly making changes to the control (eg. adding new properties) you need to continually export them to an update site, import the new update into Designer and restart, which after a few times becomes frustrating.
So to get around the issue i make a couple of changes to my local Designer configuration as described below.


Open your notes directory, navigate to the framework directory, create a new folder and name as you feel suitable (e.g. jmc-plugins). Inside the newly created folder create another folder called eclipse.
Next navigate to the framework\rcp\eclipse\links folder and create new text file named again as you feel suitable ensuring it has the extension ".link" (e.g. jmc-plugins.link)
Inside your newly created text file add the following:
  path=C\:/Program Files (x86)/IBM/Notes/framework/jmc-plugins
ensuring the path is to your newly created directory.

What the link file does is tells Designer to look in the new folder to load any additional plugins/features which will then allow you to export you plugin directly into this folder.

Unfortunately this will not work just yet as Designer by default does not allow you to plugins in this way, to resolve this you need to edit the following file
data/workspace/.config/org.eclipse.update/platform.xml
Change the transient attribute on the config tag to false and replace all the instances of policy="MANAGED-ONLY" to policy="USER-EXCLUDE"

For these settings to take effect you will need to completely close Notes/Designer/Administrator and re-open. I have read that you need to use the following when restarting Designer using the command "designer.exe -RPARAMS -clean". I can't remember doing this myself.




6 May 2014

Domino OSGI (Part 1) - Configuring Eclipse for XPages OSGI Plugins




Recently I have been writing some of my own XPage components, initially i began writing them inside domino in the nsf that was using them.
I eventually i began to put some of the controls inside other nsfs, but as always i had to make some fixes enhancements to the bugs Features that I happened to code into the components. I then had to go back to the other databases and apply these enhancements.
So, I decided it was time to write my own plugin to deploy the components to the server so they would be available to all applications also giving me one place to update.

In the next series of posts I'm going to describe how i set up my environment to create plugins using eclipse.

Part 1 - Downloading and Configuring Eclipse

Firstly download Eclipse, I use Eclipse IDE for Java EE Developers however i think Eclipse for RCP and RAP Developers is also suitable.
Extract the Eclipse folder from the downloaded zip into your desired location eg: c:\Program Files\Eclipse
I then create a shortcut on my desktop to the eclipse.exe

Next download XPages SDK for Eclipse RCP from OpenNTF and the Domino Debug Plugin from IBM. Extract the update site from the Domino Debug Plugin zip file (com.ibm.domino.osgi.debug.site.zip)

Now we have everything we can start Eclipse and configure accordingly. When you first start Eclipse you will be prompted to choose your workspace. Change if you wish or just click OK.
Once inside eclipse go to Help -> Install New Software. When the Install window loads click Add to link to a new repository. Enter a name 'Domino Debug Plugin', click Archive and select the debug plugin update site you extracted earlier.
Click ok. Expand the list and select the Domino OSGI Debug feature
Click Next -> Next -> Accept the license -> Click Finish
OK the security warning
Don't bother restarting yet. Go back and install the XPages SDK as we did with the Debug plugin above:
Once the XPages SDK is installed restart Eclipse.

Now we need to configure the XPages SDK. Goto Window -> Preferences and select XPages SDK at the bottom.
Ensure you tick Automatically create JRE and fill in the fields accordingly
Click Apply.
Next go to Java -> Compiler and drop the compliance level to 1.6 (assuming it's not there already). Click Apply.

Go to Java -> Installed JREs and select XPages Notes JRE (This will not be here if you did not tick the box above)
Go to Plug-in Development -> OSGi Frameworks and select Domino OSGi Framework. Click Apply.

Finally we need to create a Target platform, fortunately this is easy with the XPages SDK installed.
Go to Plug-in Development ->Target Platform and click Add
Select Template Notes Install Target
Click Next. Click Finish.
Now select the Target we just created and click Apply.
Click OK

There we go, we are now ready to begin creating a plugin!

7 Jan 2014

Validating field length in JavaScript

Recently i have been working on an application that requires a field with a limited number of characters (200 in this case).
However from a users perspective it is difficult to know the number characters currently used so I decided to display the number of characters:


As the text area changes the count is updated, if the limit is reached further characters are prohibited. This is achieved by using the key up to trigger a client-side javascript function:
restrictCharacterCount(displayedCountId, sourceFieldId, maxValue, allowExceedMax)
The function accepts 4 arguments; only the first 3 are required.
  1. displayedCountId - this is the id of span (computed text) element that holds the count of the characters
  2. sourceFieldId - this is the id of the text area where the number of characters are being counted from
  3. maxValue - this is the maximum number of characters that are allowed in the field
  4. allowExceedMax - this is a flag which allows the maximum number of characters to be exceeded but turns the colour of the count to red instead- this is optional.
XPage
   1:  <xp:span id="spnAbstractLabel">
   2:      <xp:label id="lblAbstract" value="Abstract" for="edaAbstract" />
   3:      <xp:div rendered="#{javascript:docApplicationDetails.isEditable()}">
   4:          <xp:text id="cmpAbstractCharacterCount" value="0" />
   5:          <xp:text id="cmpAbstractCharacterAllowed"
   6:              value="${javascript:'/'+pageController.defaultDocumentEvents.ABSTRACT_MAX_LENGTH}" />
   7:      </xp:div>
   8:  </xp:span>
   9:  <xp:inputTextarea id="edaAbstract" styleClass="lotusText"
  10:      value="#{docApplicationDetails.Astract}" rows="3">
  11:      <xp:this.validators>
  12:          <!-- Unable to use EL here for some reason -->
  13:          <xp:validateLength
  14:              maximum="${javascript:pageController.defaultDocumentEvents.ABSTRACT_MAX_LENGTH}"
  15:              message="Abstract can only be 
  16:              ${javascript:pageController.defaultDocumentEvents.ABSTRACT_MAX_LENGTH} characters long.">
  17:          </xp:validateLength>
  18:      </xp:this.validators>
  19:      <xp:this.onkeyup>
  20:          <![CDATA[applicationUtils.restrictCharacterCount(
  21:              "#{id:cmpAbstractCharacterCount}",
  22:              this.id,
  23:              #{javascript:pageController.defaultDocumentEvents.ABSTRACT_MAX_LENGTH})]]>
  24:          </xp:this.onkeyup>
  25:      </xp:eventHandler>
  26:  </xp:inputTextarea>
Javascript
   1:  function restrictCharacterCount(displayedCountId, sourceFieldId, maxValue, allowExceedMax){
   2:      var sourceElement = XSP.getElementById(sourceFieldId);
   3:      var sourceText = sourceElement.value;
   4:      var count = sourceText.length;
   5:      var countElement = XSP.getElementById(displayedCountId);
   6:      
   7:      if(count < maxValue){
   8:          //remove any styling that may have been applied by any of the other conditions below
   9:          countElement.style = "";
  10:      }else if(count == maxValue){
  11:          //if the count of characters has reached max then bold the count text
  12:          countElement.style.fontWeight = "bold";
  13:      }else{
  14:          //count must be over the threshold
  15:          if(allowExceedMax){
  16:              //Change the colour of the count element to red
  17:              countElement.style.color = "red";
  18:          }else{
  19:              reset the text to the max allowed
  20:              sourceElement.value = sourceText.substring(0, maxValue);
  21:   
  22:              //reset count the new character count; in theory should be maxValue
  23:              count = sourceElement.value.length;
  24:          }
  25:          //highlight the count incase the condition maxValue condtion has not been triggered
  26:          countElement.style.fontWeight = "bold";
  27:      }
  28:      countElement.innerHTML = count;
  29:  }

24 Nov 2013

Replacing existing file with uploaded file from FileUpload Control.

I have been creating an XPages application that contains 1 icon which is uploaded using the fileUpload Control and is displayed using an image control. Simple and straight forward.

I also decided that I wanted the image to always have the same file name for ease of later use; this was the result:
<xp:fileUpload id="fupIcon" value="#{docApplicationDetails.Icon}"
    useUploadname="false" filename="icon.jpg">
    <xp:eventHandler event="onchange" submit="true"
        refreshMode="complete">
    </xp:eventHandler>
</xp:fileUpload>
<xp:image id="imgIcon">
    <xp:this.url><![CDATA[#{javascript:try{
    if(docApplicationDetails.getAttachmentList("Icon").length > 0){
        return docApplicationDetails.getAttachmentList("Icon")[0].getHref();
    }
}catch(e){
    return null;
}}]]></xp:this.url>
</xp:image>
 The above was great; it uploaded the image and displayed it. However if you upload another image it still displays the first image which is exactly what i had told it to do:
docApplicationDetails.getAttachmentList("Icon")[0].getHref()

To get 1 upload only I could have used this snippet posted by Sven Hasselbach
Unfortunately, this snippet was not suitable for me as it involves saving the document so I decided to use this:

if(docApplicationDetails.getAttachmentList("Icon").size() > 1{
    //remove the previous attachment
    var i = docApplicationDetails.getAttachmentList("Icon").size()-1;
    docApplicationDetails.getAttachmentList("Icon").remove(i);
}
This was also not suitable as the first image that gets uploaded gets named 'icon.jpg' but the next image is named 'icon_1.jpg'.

Finally, I decided the best approach would be to remove the first attachment before the second is uploaded and the way I decided to do this was using a validator. The validator is ran in the page lifecycle before the data model is updated (file is uploaded to the document).
This was the final result:
XPage:
<xp:fileUpload id="fupIcon" value="#{docApplicationDetails.Icon}"
    useUploadname="false" filename="icon">
    <xp:this.validator><![CDATA[#{pageController.defaultDocumentEvents.validateIconUpload}]]></xp:this.validator>
    <xp:eventHandler event="onchange" submit="true"
        refreshMode="complete">
    </xp:eventHandler>
</xp:fileUpload>
<xp:image id="imgIcon">
    <xp:this.url><![CDATA[#{javascript:try{
    if(docApplicationDetails.getAttachmentList("Icon").length > 0){
        return docApplicationDetails.getAttachmentList("Icon")[0].getHref();
    }
}catch(e){
    return null;
}}]]></xp:this.url>
</xp:image>
Validator:
public void validateIconUpload(
        FacesContext facesContext, UIComponent component, Object value) throws ValidatorException {
    //using validation to remove existing upload before allow new one
    DebugToolbar.get().info("Validating Icon Upload");

    try {
        //check if there is already an uploaded attachment
        DominoDocument docApplicationDetails = JSFUtil.getXspDocument("docApplicationDetails");
        if(docApplicationDetails.getAttachmentList("Icon").size() > 0){
            //remove the existing attachment
            docApplicationDetails.removeAllAttachments("Icon");
        }
    } catch (NotesException e) {
        FacesMessage message = new FacesMessage("An Error occurred whilst uploading the Icon");
        // Throw exception so that it prevents icon being uploaded
        throw new ValidatorException(message);
    }
}

BxSlider