Workaround for updating the styleClass property of a button when using BS theme

In my application I am using the Bootstrap theme that comes with the extension library. In the application I have a button with which I want to enable/disable the appearance of the debugtoolbar plugin for the end-user so it will be easier to handle incident reports when they should occur.

So in my application layout control I added the toolbar and compute the appearance:

debuglayout

Now somewhere in my application I have in a dialog a button to show/hide the debug toolbar:

debugBtn In the browser it looks as followed:

debugDlg

At first when I pressed the button the styleClass would update ONLY when I totally refreshed the page which was undesired.

The trick here is to disable the theme for the button:

debugTheme

I then have to surround the button with additional HTML span element:

<span class=”btn-group”><xp:button…/></span>

As a result the button changes styleClass correctly:

debugSwinging.JPG

Want to know more about XPages dev item? Just drop a question below…

Debugging utility in XPages

Time to re-open an previous XPages project and add some new functionalities for the users. When I did this today I noticed some programming that I never wrote about before, so I do this now 🙂

In an application I wanted to monitor individual classes and write the findings to the console or OpenLog. All managed from a web UI 🙂

The printing method in this app is pretty simple:

public void printToConsole(String msg){
String callerClassName = getCallerCallerClassName();
if (debugClasses.containsKey(callerClassName)){
if (false != debugClasses.get(callerClassName)){
if (true == this.openLog){
OpenLogUtil.logEvent(new Throwable(), msg, Level.INFO, null);
}
else{
System.out.println(msg);
}
}
}
}

The getCallerClass method identifies which class is calling this method:

public static String getCallerCallerClassName() {
StackTraceElement[] stElements = Thread.currentThread().getStackTrace();
String callerClassName = null;
for (int i=1; i<stElements.length; i++) {
StackTraceElement ste = stElements[i];
if (!ste.getClassName().equals(KDebug.class.getName())&& ste.getClassName().indexOf(“java.lang.Thread”)!=0) {
if (callerClassName==null) {
callerClassName = ste.getClassName();
} else if (!callerClassName.equals(ste.getClassName())) {
return ste.getClassName();
}
}
}
return null;
}

Here is the KDebug class :

package com.wordpress.kwintessential.myproject.utils;

public class KDebug {
public static String getCallerClassName() {
StackTraceElement[] stElements = Thread.currentThread().getStackTrace();
for (int i=1; i<stElements.length; i++) {
StackTraceElement ste = stElements[i];
if (!ste.getClassName().equals(KDebug.class.getName()) && ste.getClassName().indexOf(“java.lang.Thread”)!=0) {
return ste.getClassName();
}
}
return null;
}
}

debugClasses is declared as followed:

private HashMap<String,Boolean> debugClasses = new HashMap<String,Boolean>();

And it is set when I initiate my utility class that contains the printing method:

public void setDebugClasses(){

try {
Properties debugs = this.getDebugProperties();
Enumeration<String> enums = (Enumeration<String>) debugs.propertyNames();

while (enums.hasMoreElements()) {
Boolean boolean1 = false;
String key = enums.nextElement();
String value = debugs.getProperty(key);
boolean1 = Boolean.valueOf(value);
debugClasses.put(key, boolean1);
}
} catch (IOException e) {
OpenLogUtil.logError(e);
}

}

method getDebugProperties reads a properties file that is stored in the WebContent directory:

monitor

public Properties getDebugProperties() throws IOException {
InputStream input = FacesContext.getCurrentInstance().getExternalContext().getResourceAsStream(“monitor.properties”);
Properties debugs = new Properties();
debugs.load(input);
return debugs;
}

Note that I have to place the file in the WebContent folder and I could not place it as a Resource design element.

The monitor.properties file contains key values pairs e.g.:

com.wordpress.kwintessential.myproject.dao.DecisionDominoDAO=true
com.wordpress.kwintessential.myproject.dao.VisitorDominoDAO=false

So with this code above I can:

  • check which class is calling the print method
  • check if this class should be monitored (read: print data to the console or OpenLog)

So all great! But in my case I wanted to prevent that the design had to be opened and rebuild every time the monitor settings should be altered.

So in this project I created a web UI (XPage) to read and change the monitor.properties file:

<?xml version=”1.0″ encoding=”UTF-8″?>
<xp:view xmlns:xp=”http://www.ibm.com/xsp/core”&gt;

<xp:this.beforePageLoad><![CDATA[#{javascript:var file = param.get(“filename”)
if (null == file || file == “”){
ctx = facesContext.getExternalContext();
ctx.redirect(facesContext.getExternalContext().getRequest().getRequestURL() + “?filename=monitor.properties”);
}}]]></xp:this.beforePageLoad>
<xp:panel styleClass=”container”>

<h1>Debug <small> Monitor Java classes</small></h1>

FileName:
<xp:inputText
id=”inputTextFileName”
value=”#{fileBean.fileName}”
defaultValue=”#{param.filename}”
disabled=”true” />
<xp:br />
FileData:

<xp:inputTextarea
id=”inputTextarea1″
value=”#{fileBean.fileData}”
rows=”20″
cols=”80″ />
<xp:br />
<xp:panel styleClass=”btnBar”>
<xp:button value=”Load” id=”buttonLoad”
styleClass=”btn-primary”>
<xp:eventHandler event=”onclick” submit=”true”
refreshMode=”complete” action=”#{fileBean.loadData}”>
</xp:eventHandler>
</xp:button>
<xp:button value=”Save” id=”buttonSave”>
<xp:eventHandler event=”onclick” submit=”true”
refreshMode=”complete” action=”#{fileBean.saveData}”>
</xp:eventHandler>
</xp:button>
</xp:panel>
</xp:panel>
</xp:view>

Here is how it looks in a browser:

web ui initial.JPG

When I press the Load button the content of the monitor.properties file get’s loaded:

web ui loaded

And of course when I press the Save button the monitor.properties file get’s updated 🙂

Now where is the magic in this? I got two methods fileBean.loadData and fileBean.saveData.

Here is what the fileDataBean class looks like:

import java.io.IOException;
import java.io.Serializable;
import java.util.Properties;

import lotus.domino.NotesException;

import com.paulwithers.openLog.OpenLogUtil;

public class FileDataBean implements Serializable {

private static final long serialVersionUID = 1L;
private String fileName;
private String fileData;
private String dbPath;
private String dbServer;

private Utils utils;
Properties props;

public FileDataBean() throws NotesException{
utils = new Utils();
utils.printToConsole(this.getClass().getSimpleName().toString() + ” – FileDataBean() // constructor”);
try {
props = utils.getDataSourceProperties();
dbServer = props.getProperty(“server_namis_notesname”);
dbPath = utils.getSession().getCurrentDatabase().getFilePath();
} catch (IOException e) {
OpenLogUtil.logError(e);
}
}

public String getDbPath() {
return dbPath;
}

public void setDbPath(String dbPath) {
this.dbPath = dbPath;
}

public String getDbServer() {
return dbServer;
}

public void setDbServer(String dbServer) {
this.dbServer = dbServer;
}

public void setFileData(String fileData) {
this.fileData = fileData;
}

public String getFileData() {
return fileData;
}

public void setFileName(String fileName) {
this.fileName = fileName;
}

public String getFileName() {
return fileName;
}

public void loadData() {
this.fileData = NAPIUtils.loadFile(this.dbServer, this.dbPath, this.fileName);
}

public void saveData() {
NAPIUtils.saveFile(this.dbServer, this.dbPath, this.fileName, this.fileData);
}
}

The NAPIUtils looks as followed:

import java.io.InputStream;
import com.ibm.designer.domino.napi.NotesAPIException;
import com.ibm.designer.domino.napi.NotesDatabase;
import com.ibm.designer.domino.napi.NotesNote;
import com.ibm.designer.domino.napi.NotesObject;
import com.ibm.designer.domino.napi.NotesSession;
import com.ibm.designer.domino.napi.design.FileAccess;
import com.paulwithers.openLog.OpenLogUtil;

public class NAPIUtils {

/**
* loads a given WebContent file and returns the result as String
*
* @param serverName
* the server to use
* @param dbPath
* the database path
* @param fileName
* the file to load
* @return the file data as String
*/
static public String loadFile(final String serverName, final String dbPath, final String fileName) {
NotesSession nSession = null;
NotesDatabase nDatabase = null;
NotesNote nNote = null;
try {
nSession = new NotesSession();
// open database
nDatabase = nSession.getDatabaseByPath(serverName + “!!” + dbPath);
nDatabase.open();
// load existing data
nNote = FileAccess.getFileByPath(nDatabase, fileName);
// get Filedate and return String
InputStream is = FileAccess.readFileContentAsInputStream(nNote);

return convertStreamToString(is);
} catch (NotesAPIException e) {
OpenLogUtil.logError(e);
} finally {
// recycle NAPI objects
recycleNAPIObject(nNote, nDatabase, nSession);
}

return fileName;
}

/**
* loads a given WebContent file and returns the result as String
*
* @param serverName
* the server to use
* @param dbPath
* the database path
* @param fileName
* the file to load
* @param fileData
* the data of the file
*/
static public void saveFile(final String serverName, final String dbPath, final String fileName, final String fileData) {

NotesSession nSession = null;
NotesDatabase nDatabase = null;
NotesNote nNote = null;

try {
nSession = new NotesSession();
// open database
nDatabase = nSession.getDatabaseByPath(serverName + “!!” + dbPath);
nDatabase.open();
// load existing data
nNote = FileAccess.getFileByPath(nDatabase, fileName);
// store them to note
FileAccess.saveData(nNote, fileName, fileData.getBytes());
} catch (NotesAPIException e) {
OpenLogUtil.logError(e);
} finally {
// recycle NAPI objects
recycleNAPIObject(nNote, nDatabase, nSession);
}
}

/**
* converts an input stream to a string
*
* @param is
* the input stream to convert
* @return String
*/
static String convertStreamToString(java.io.InputStream is) {
java.util.Scanner s = new java.util.Scanner(is).useDelimiter(“\\A”);
return s.hasNext() ? s.next() : “”;
}

/**
* recycleNAPIObject helper method for recycling NAPI objects
*
* @param nObjects
* the NAPI objects to recycle
*/
static void recycleNAPIObject(NotesObject… nObjects) {
for (NotesObject nObject : nObjects) {
if (nObject != null) {
try {
(nObject).recycle();
} catch (NotesAPIException ne) {
OpenLogUtil.logError(ne);
}
}
}
}
}

From this class I use the loadFile and saveFile methods. Look at the import of the com.ibm.designer.domino.napi classes which enables me to access files within the NSF!

Hopefully this writing gives you some new ideas for your development projects. Happy coding 🙂

Quickly adding localization to an XPages app

I needed to quickly make an application available in multiple languages. Luckily I had all strings already gathered in a properties file. So the following things I needed to add:

  • A translated version of the strings properties file
  • Some sort of navigation to choose another language than the browser default one.

For the first step Google Translate was my friend. Step two was not so hard either:

context.setLocaleString(‘en’)
context.reloadPage()

So a couple of minutes work and a surprised customer 🙂

Capture

Domino2025 webinar – a technology U-turn?

In the last webinar around #Domino2025 time was spent on announcing NodeJS to/for/on Domino, a new search engine, NSF improvements but very little was discussed about the AppDev environment.

Today we have XPages as AppDev tool. Nothing about the lifecycle of this was announced. Or embracing an open source UI framework for JSF like Primefaces.

Some words about an “open IDE” environment which might be good to have the option to choose your favorite development environment. But the main problem I have that the whole webinar sounded like a complete U-turn in technology.

What will be your options as a Java developer?

Should you dump your learned skills and switch over to JavaScript and React, Angular or Vue?

Is there hope that JSF on Domino will be upgraded to a recent version so you can bring in new opportunities to your applications?

What must companies do with their existing XSP applications? Re-write them (again)?

To me it sounded IBM is keeping its hands of from developing and maintaining any development framework. The “traditional” Domino development framework with forms, views, pages, subforms is left dead. The “Web 2.0” Domino development framework with XPages and Java will be left for dead. The only thing that remains is a platform where applications reside, services like mail, replication, http, etc runs on. BUT WHAT ABOUT APPDEV?

I am not aware if there are any drag and drop IDE’s for react or angular but the power to Notes was and has always been rapid application framework. I build applications in close relation to my customer (an IT manager or a LOB manager) which does not have the time or competence to set up and gather all the specifications in front. It is a journey of implementing capabilities and discovering opportunities. I may develop a new idea within an hour or rebuild or remove another idea within another hour.

I heard nothing about this during the webinar!

Lesson learned in ReplaceItemValue vs NotesItem

Introduction

In a project I am working on I am modernizing a Domino application with XPages and JAVA MVC. To support the workflow in the application a “main” document must have at least 1 approved “sub” document for 9 mandatory areas. These “sub” documents appear then in a Notes view and an agent is keeping the overview if all 9 areas have approved documents .

The selection formula for this view is something as followed:

SELECT Form=”formA” & status=”40″ & notAuthorized = “”

replaceItemValue

To add a field to a document I normally use ReplaceItemValue as described in the XPages wiki.  The Help in Designer says about this method:

Replaces all items of the specified name with one new item, which is assigned the specified value. If the document does not contain an item with the specified name, this method creates a new item and adds it to the document.

So that sounded that I was good to go. Except… the documents created by my script did not appear in my view.

Document properties

When I compared the documents with ScanEZ / looked at the Document Properties tab / created on output via Document Viewer I could not find an explanation why the documents should not match the View selection formula 😕

Modifying the selection formula to something outrageous like

SELECT Form=”formA” & status=”40″ & notAuthorized != “SOMEOUTRAGEOUSCONDITION”

resulted that the documents appear in the view.

But I would rather like to keep the current data model of the View as is in place so the current code/UI could not become disturbed in any way.

Notes Item

Then I decided to change my code from

doc.replaceItemValue(“notAuthorized”, “”);

into

Item textItem = doc.replaceItemValue(“notAuthorized”, null);

textItem.setValueString(“”);

textItem.setSummary(true);

and I noticed the documents appear now in the Notes View.

When I compare the two methods in the replaceItemValue method the field notAuthorized is empty but has a data-length of 1. In the Item method the field is also empty BUT the data-length is 0.

So something is added to the field. But what?

IsSummary

First I thought perhaps it’s due to the Summary property but in both methods the Summary property set (at least it looks like that).

Here is some more information from Designer Help:

 

The IsSummary property of the new item defaults to true, which means that the item value can be displayed in a view or folder.

So I am wondering if the replaceItemValue (in Java) is properly setting this property or maybe not?

 

Presentation JUMP Session: IBM Domino Applications on Cloud

Abstract

IBM Domino Applications on Cloud is a subscription service that offers IBM hosted solution for Domino Apps over IBM Bluemix Cloud. The service offers a structured and planned migration process, avoiding business disruptions.

In this session you will be introduced to the offering, how it is structured and works including the pre-requisites, configurations and start-up options.

The video recording can be found here.

#Domino2025 Jam ended- Some first thoughts

Today I participated in the Domino2025 Jam here in Stockholm. The Jam is organised via a set of workshops around Application Development and Mail & Chat. I will not go into much details to cause a possible spoiler for you but I just want to mention a particular suggestion by an IBM Business Partner.

The suggestion was to tackle all the discussion / lost space / future of corporate mail to simply abandon the area and leave the space solely to the competitor.

What should you think of that? To me it would say that if IBM is not capable of providing a qualitative and competitive solution (mail & calendaring client + server)  for Mail I seriously doubt what the capabilities of IBM are.

The Notes client on expeditor has never been the desired success (slow startup, significant code base, complexity to governance in combination with bad or lack of marketing and complexity in the UX) but developing a HTML5 (Domino) Mail client with a ID vault to ensure encryption would be doable right?

This would prevent the competitor to walk freely through the door of every customer and spreading their other competitive products. It is also a slap in the face for customers who have invested heavily in the IBM portfolio.

The idea to separate Mail from the Applications I believe is a widely accepted alternative future for the client.

Domino has some excellent goodies regarding mail & calendaring. A too big waste to throw overboard that easily. But that is not what this business partner specialized in migration wants to broadcast via these DominoJams.

So be alert!

Thank you IBM for the invitation and gathering awareness of the continuity of the Notes/Domino roadmap.