Thursday, January 28, 2010

XPages: Using the powerful Design Definition in custom controls

I've recently started using the Design Definition in my custom controls to save space on the XPages they are included in.

When I first encountered this new (since Domino 8.5.1) feature, I believed it was a static feature. It turns out it's highly dynamic, and quite powerful.

Immediately, when I read the "Providing illustration for your XPage custom controls" article from the Lotus Notes and Domino Application Development wiki, I thought of JSP. What reminded me of JSP was this little snippet:
<%=this.viewName %>

I've only tested if/else statements, as I know very little JSP/haven't had the need for anything else. If you know JSP/have a blog, please test out this feature, and share your findings.

Included in todays demoapp, there is a custom control that lets you open up a "remote" Dojo dialog (dijit.Dialog). The developer can choose between using a link and a button as the "launcher". This is controlled by the property definition in the custom control.

The design definition for the control:
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<% if(this.activationControl==='link' ){ %>
<a href="#"><%=this.showDialogLabel || 'DojoDialogLink' %></a>
<% } else { %>
<button>
<%if( this.btnImage ){%><img src="<%=this.btnImage %>" /><%}%>
<%=this.showDialogLabel || 'DojoDialogButton' %></button><%
}%>
</xp:view>


this is (amongst other things?) the compositeData-object that contains all the properties from the property definition. this.btnImage in the design definition is the same as compositeData.btnImage in the custom control.

If you set the custom control to use a link to launch the dialog, the custom control shows up as a link in the XPage it's included in. If you change that to a button, immediately the representation of the custom control changes into a button. If you haven't typed a label for the link/button, it has the label DojoDialogLink/DojoDialogButton, depending of the action type you've chosen. When you type in a label, the link text/button label changes correspondingly. If you've chosen an image for the button, the image shows up in the XPage.

Flash video of the design definition in action:



>> Small demoapp

Share and enjoy!

Small bugfix for the XPages API tool

When you inspected a class, using the class name, e.g. java.util.ArrayList, you got the API for java.lang.Class every time. This is now fixed.

Also, when you write the name of a global object, e.g. view, the class name of the object is now the header for the table containing the methods/properties.

>> Original post with API tool

>> Download tool

Tuesday, January 26, 2010

XPages: Custom Control that can help prevent save conflicts

Today, I share with you a demoapp with a basic "has document been modified validation" (in lack of a better description) custom control, ccValidateModified.

Just pop it into an XPage with a document data source, and it should in theory work out of the box. Plug and play.

This is by no means an absolute protection against save conflicts. For applications that it's critical to avoid save conflicts, you need better protection.

Below is a movie of the control in action. A document is open in Opera, Firefox and Internet Explorer. When the document is saved in one of the browsers, the "other browsers" gets a dialog saying that the document has been modified.


(Click the play-icon to start. Broadband recommended.)

The way it works is that a scoped variable with the modified-time from the stored doc is set on page load (editmode).

When the window gets focus/every two seconds (setInterval), a partial refresh is initiated. If the doc has been modified since the document was opened for editing, a hidden image is loaded. The image has an onload event (easiest way to make it cross browser) -> show a modal dojo dialog.

>> Download demoapp with custom control

Share and enjoy!

Office 2007 MIME headers fixed in Domino 8.5.2

Just got word that IBM finally have implemented the Office 2007 MIME headers.

Monday, January 25, 2010

Small update on the Xpages search demo

The xpages search demo I posted last week is still messy, but I've cleaned it up a tiny bit.

I've added another parameter in the config for the search-method. You can now specify fieldName separately from the formula. I actually documented it, but forgot to implement it in the demo.

This should make it easier to refer to fields in the columns.
E.g.
TemplateSearch.search( [{ 
fieldName: 'name',
formula: 'LastName + ", " + FirstName',
dataType: 'string'
},{
fieldName: 'modified',
formula: '@Modified',
dataType: 'date'
}]);

If you want to have a demo of something more in regards search-results/data tables, leave a comment, and I'll take a look at it. If it's something I can do in a relatively short amount of time, I'll make/post a demoapp with a simple description.

Saturday, January 23, 2010

Comments are now open

I've got a few comments over the time of my blog that people can't comment because they don't have a google account.

I've now opened for anonymous comments, so if you want to ask me about something in regards to any of my blogposts, now's your chance.

The reason I restricted comments back in the day was because I was afraid of dealing with spam all day. Let's see how it turns out.

Wednesday, January 20, 2010

XPages: Three ways to build a search interface with "on the fly" sortable columns

While copy/paste programmers and XPages novices might get something out of this demoapp/blogpost, it's directed towards the people that want to push XPages beyond the constraints of the drag and drop interface in Domino Designer.

I detest the "need" to have a Notes view for every kind view in XPages. Recently, at work, I've been occupied mostly by creating customizable search/filtering views in XPages.

Until today, I've used a DataTable with a SSJS function as data source (returns JS-objects). The function does an FTSearch on a NotesView, and fetch precalculated JSON strings from a view column. The DataTable can be designed to use one/all fields from the json-objects. One DataTable column can have one or more values from the JSON object (e.g. dataItem.fieldName + ' ' + dataItem.fieldName2). This has the advantage of only needing a one (unsorted) column view to generate a wide array of reports.

Julian Buss seems to have been working on/pondering solutions for "on the fly" sortable searches as well. I've implemented what I think he meant in the demoapp as well. I didn't take the time to write caching algorithms for his way, using NotesViewEntryCollection. I also didn't take the time to implement descending sort. The reason I was so lazy was that I didn't like my implementation of his idea.

The last way (default choice in demoapp), and the best way I've found so far is to do FTSearch on the db. In the demoapp, I search the entire db without restricting to a form/etc. This is because I didn't find the need for restricting the search as there are only one kind of documents in the demoapp. The result from the search is a NotesDocumentCollection. The datasource is a function that accepts what I call an array of field definitions.

E.g.
var fieldDefinitons = [{
fieldName: 'someFieldName',
dataType: 'string'
},{
fieldName: 'someFormulaString',
dataType: 'date'
}]


Supported datatypes in the demoapp are string, multi-value string, date, multi-value date, number and multi-value numbers. The field definitions are parsed into a string that can be evaluated against a document (using session.evaluate). Using session.evaluate is the fastest way I know of to get multiple values out of a document. Compared to fetching precalculated JSON strings from a view, there is no discernible difference in speed when evaluating against documents in the NotesDocumentCollection.

Regarding caching, the JSON object is stored in a viewScope-variable. When the user navigates to another page, the JSON object should be recycled from what I understand of XPages/JSF. The values are only fetched from the server when the query string is changed. When the used wants to sort the view, the cached object is resorted. This results in speedy pagination/sort.

On my local computer, with 5000 items in the result-object, re-sort is done in about 100ms on the server. It probably takes 500-1000ms before the user gets what he ordered. The initial search takes 2-4 seconds (server time).

I could probably write a tiny book on the "technology"/thought/history behind this demoapp. Instead, I'll tease you with an animated gif, and let you download a demoapp (here be messy code). The demoapp is a biggie. It's a mutilated version of Jake's FakeNames application. I chose this because I wanted to test against a "big" database. This has 40k NAB Person documents.

Screenshots (animated gif):


>> Demoapp (6 MB zip)

Share and enjoy!

Wednesday, January 13, 2010

SSD - The best Lotus Notes/Eclipse can get?

As some of you might know, Notes/Domino in Eclipse contains a lot of files. I believe it was somewhere around 35 000 for Notes/Sametime/Domino Designer 8.5.1.

Random access time (the time it takes to find/access a random file) is where mechanical disks probably are at it's weakest. This is especially noticable when you want to access a lot of files "at once", like when you want to start Notes cold (not started since booting the computer).

When the first SSDs came out, the test results were lousy. After reading about ten tests the initial months after SSDs became commercial, I more or less forgot about SSDs.

Then I read this. Cold starting Lotus Notes in 4 seconds?!?!

To be certain, I nagged my boss into testing out an SSD at work. I ended up with a Corsair, which has good write and read speeds. I don't think I'm exaggerating when I say that it felt like a new computer. I'm not sure my work computer starts Notes below five seconds (Core 2 Duo/1.7GHz), but it's well below 10 seconds.

What probably made me the most happiest is that during the weekly antivirus scan, I could work as normal. With the mechanical disk, it felt like working on a computer from the 20th century.

Then I decided it was time to get an SSD for my desktop computer at home. After reading a test at tomshardware.com, I decided on a 80GB Intel X25M G2. It has decent write speed, and great read speed.

I'm happy I decided on that disk after reading this performance comparison (at the time of writing, Intel X25M G2 at the top).

When SSDs become as cheap as mechanical drives, it's going to do a lot for servers worldwide.

If you're still not convinced (10 000 RPM IDE disk vs SSD):

XPages: Developer Tool - Deep dive into the XPages API

If you work on XPages, and are going to download only one demoapp from me in your life, this is the one you should download (at least compared to my earlier apps).

On several occasions, Domino Designer falls short when it comes to developing XPages. On those occasions I try to dive into the XPages API. I used to create a computed field with the value of typeof getComponent( 'component-id' ). This gave me the class name of the object. Then I declared a variable with the type of the class. This gave me the methods available for the component type. E.g.
var field:com.ibm.xsp.component.xp.XspInputText = new com.ibm.xsp..
Today, working on inspecting an Exception object, trying to find a way to print ServerSide JavaScript stack trace (you only get the Java stack trace), I came up with an idea that will save me a lot of time in the future of XPage development. I didn't find a pay to get the JS stack trace, if you're wondering.

It started with a simple field that you put a class name into. This gave you the properties and methods of the class.

Then I added the functionality to type the name of a global object (e.g. facesContext), and it printed the properties/methods of that object.

Then, while writing/preparing this blog post, I remembered my client side getClientId/getComponent. After a little bit of copy/paste/modify, I ended up with a custom control that lets you select any control (including event handlers/etc) on the page from a combobox (just add the custom control to the XPage you're developing), and get the methods and properties of the corresponding class in the XPages API.

A couple of screen captures (animated gif):


>> Download Demoapp with custom control (28K zip)

In theory, the custom control lets you post commands to the server, so remember to modify the ACL after you download!

Share and enjoy!

It's about time you update the NotesDatabase.Search documentation, IBM!

A colleague of mine was working on NotesDocumentCollections resulting from a NotesDatabase.Search. He discovered that whatever you put into the last parameter (maxDocs), all documents were returned.

Here's the documentation on the Search method:

Syntax:

Set notesDocumentCollection = notesDatabase.Search( formula$, notesDateTime, maxDocs% )

Parameters:

formula$

String. A Notes @function formula that defines the selection criteria.

notesDateTime


A cutoff date. The method searches only documents created or modified since the cutoff date. Specify Nothing to indicate no cutoff date.

maxDocs%

Integer. The maximum number of documents you want returned. Specify 0 to receive all matching documents.


The documentation on maxDocs is wrong, and IBM has been aware of the issue since Notes 4.5.

Do Quality control much?

Tuesday, January 12, 2010

Workaround for radiobuttons changing values on page refresh in Firefox

I had some issues with Firefox changing radio button values on page refresh. Found this workaround description.

Monday, January 11, 2010

Further investigation of using FIELD in a Page

I got a little fired up by my previous find on using FIELD for declaring global variables in a Page. Therefore I did a little more digging.

FIELD-declared variables does not leak into embedded views, nor do FIELD-declared variables in view selection leak up to the containing page.

FIELD-declared variables leak down to an embedded outline when you display a page in the Notes Client and on the web, HTML style. If you show the embedded outline using a Java Applet, it doesn't work.

FIELD-declared variables in an outline are globally available to the items below the outline item it's declared in (regardless of indentation), in the Notes Client. They are not globally available for web applications, regardless of HTML/Java rendering. E.g. outline item 1 has a FIELD-declared variable -> every outline item after outline item 1 can use it (Notes only).

Use FIELD in pages to declare global variables

Edit: It only seems to work when the page is set to Content-Type: Notes, which is a drag (needed it for a custom content-type/JS-page)..

What I've probably longed most for in pages is the ability to have global variables. I thought this was impossible due to the lack of fields in a page.

It turns out it's quite simple. The reason I came up with trying this is Andre Guirards article on Using View Column Programmatic Names. In this article (recommended read!), he discusses the usage of FIELD in view columns/selection formulas to create temporary global variables.

To declare a variable as global, put FIELD in front of it.
FIELD globalVariable := "I'm global!";
For cross client (Notes/Web) pages, it seems that you have to put the global variable declarations in "Window Title". First declare your variables using FIELD, then add the code for the window title.

Declaration:


Using the variable in a computed text:


Notes:


Web:

Thursday, January 7, 2010

Bookmarklet that lets you listen to all dojo events

This was originally reported by Ajaxian. The original page with the code has disappeared/or is down.

I found the code for the bookmarklet here.

Bookmarklet

XPages: Hijacking/publishing partial refreshes globally

Update 11.10.2010 Found another bug. onComplete can sometimes be a string. Fixed.

Update 07.05.2010 Fixed a bug where the onStart/onError/onComplete event were overwritten.

This is a modification to Jeremy Hodges Adding the Ability to Watch for ANY Partial Refresh in an XPage.

The difference between this and Jeremy's code is that this lets you have multiple listeners to all stages of the partial refresh (init, start, complete and error).

function hijackAndPublishPartialRefresh(){
// Hijack the partial refresh
XSP._inheritedPartialRefresh = XSP._partialRefresh;
XSP._partialRefresh = function( method, form, refreshId, options ){
// Publish init
dojo.publish( 'partialrefresh-init', [ method, form, refreshId, options ]);
this._inheritedPartialRefresh( method, form, refreshId, options );
}

// Publish start, complete and error states
dojo.subscribe( 'partialrefresh-init', function( method, form, refreshId, options ){

if( options ){ // Store original event handlers
var eventOnStart = options.onStart;
var eventOnComplete = options.onComplete;
var eventOnError = options.onError;
}

options = options || {};
options.onStart = function(){
dojo.publish( 'partialrefresh-start', [ method, form, refreshId, options ]);
if( eventOnStart ){
if( typeof eventOnStart === 'string' ){
eval( eventOnStart );
} else {
eventOnStart();
}
}
};

options.onComplete = function(){
dojo.publish( 'partialrefresh-complete', [ method, form, refreshId, options ]);
if( eventOnComplete ){
if( typeof eventOnComplete === 'string' ){
eval( eventOnComplete );
} else {
eventOnComplete();
}
}
};

options.onError = function(){
dojo.publish( 'partialrefresh-error', [ method, form, refreshId, options ]);
if( eventOnError ){
if( typeof eventOnError === 'string' ){
eval( eventOnError );
} else {
eventOnError();
}
}
};
});
}

Run the above function at the top of your application script to hijack and publish the partial refresh event.

Example usage:
dojo.subscribe( 'partialrefresh-init', null, function( method, form, refreshId ){
alert('Partial refresh for ' + refreshId + ' initiated.' );
} );

dojo.subscribe( 'partialrefresh-start', null, function( method, form, refreshId ){
alert('Partial refresh for ' + refreshId + ' started.' );
} );

dojo.subscribe( 'partialrefresh-complete', null, function( method, form, refreshId ){
alert('Partial refresh for ' + refreshId + ' complete.' );
} );

dojo.subscribe( 'partialrefresh-error', null, function( method, form, refreshId ){
alert('An error occured during partial refresh of ' + refreshId + '.' );
} );

Documentation for dojo.subscribe

Wednesday, January 6, 2010

XPages: Binding customizable scoped variables to fields in custom controls

Edit 13.09.2012: As commenter, Bas van Gestel pointed out. This method of doing things is outdated. I can't remember the reason why I had issues, but this is a better way of doing it (where fieldName is a custom property):
#{viewScope[compositeData.fieldName]}
I stumbled onto a weakness(?) in the XPages editor today. I have a complex field that I use twice in an XPage. The only difference between the two instances of the field is the rendered-property, and the data binding. To avoid copy/pasting(/screwing up in the long run), I decided to make a custom control of the field.

The rendered-property is set on the custom control, instead of the field. The data binding is a scoped variable. I couldn't find a way to compute the name of the scoped variable in the data binding section. To work around this (flaw?), I set the data binding afterPageLoad.

Here's the code:
// Set value-binding when page loads
var application = facesContext.getApplication();
var scopedField = compositeData.scopedField;
var valueBinding = application.createValueBinding( '#{viewScope.' + scopedField + '}')
getComponent( 'text-filter' ).setValueBinding( 'value', valueBinding );
text-filter is the id of the field.
scopedField is the custom property that defines the name of the scoped variable

If you have a custom control that's used many times inside a page, you may need private scoped variables.

Share and enjoy!