Tuesday, December 13, 2011

ClassNotFoundException with the new Java design element

Last week Vince Shuurman blogged about having to recompile when opening an XPage app in Domino Designer.

I had the same issue. I was using some Java code in an XPage, and every time I opened the app in designer, I got ClassNotFoundException when opening the XPage. A build of the project fixed the issue.

My java code was in the new Java design element (new in Domino 8.5.3), so I suspected that it might have something to do with this.

I moved the code to a "custom" java source folder, and the error went away. Closing/opening the app in Designer did not result in ClassNotFoundException.

Friday, October 21, 2011

Java Debugging in Designer without hacks

I found this today: How can I enable Java debugging?.

Not sure if this is new in 8.5.3, but I never heard of it. It makes it a lot easier to debug than the using the two headed beast method which seemed like too much trouble.

The full instructions are in the Designer help. Search for java debugging.

Wednesday, September 28, 2011

Collecting data for HTTP hang or performance issues on a Lotus Domino server

We're currently having problems with one of our old Domino servers. The HTTP task randomly hangs.

In the process of looking for help to track down the reason, I found this document from IBM.

Collecting data for HTTP hang or performance issues on a Lotus Domino server

Tuesday, September 27, 2011

Indicator for all partial refreshes

Sometimes partial updates take a while. To make users aware of updates happening, I made a small JS object that automatically shows a dojox.widget.Standby over the area being updated. Initially I thought that it would be too much, showing the mask over every refreshed area. So far, I quite like the effect.

The app isn't in production yet, so I don't know how users will react, but hopefully they will appreciate being made aware of that things are happening.

To load the object, put this in a JavaScript library (client side)
dojo.addOnLoad(function(){ new StandbyWidget(); });
The default background color is bright yellow. To override, simply put a hex string in the "constructor" call.
dojo.addOnLoad(function(){ new StandbyWidget( '#555'); }); // Dark grey
To use the code snippet below, you also need the partial event hijacker
var StandbyWidget = function( backgroundColor ){
dojo.require( 'dojox.widget.Standby' );
this.widget = new dojox.widget.Standby();
this.widget.attr( 'color', backgroundColor || '#ffe' );

document.body.appendChild( this.widget.domNode );
this.widget.startup();

dojo.subscribe( 'partialrefresh-init', this, function( method, form, targetId ){
if( targetId && targetId !== '@none' ){ this.show( targetId ); }
});

dojo.subscribe( 'partialrefresh-complete', this, function( method, form, targetId ){
if( targetId && targetId !== '@none' ){ this.hide(); }
});

dojo.subscribe( 'partialrefresh-error', this, function( method, form, targetId ){
if( targetId && targetId !== '@none' ){ this.hide(); }
});
}

StandbyWidget.prototype = {
show: function( targetId ){
this.widget.attr( 'target', targetId );
this.widget.show();
},
hide: function(){
this.widget.hide();
}
}

Monday, September 26, 2011

Using themeId for maintainability

In an application I'm currently working on, there are several categorized views with number-/totals columns. As the number of views/columns increased, I looked for a way to make styling of the columns more maintainable.

The solution I found was using themeId on the columns and calling a SSJS function in the theme, that generates the style classes.

I chose numberColumn as the name for the themeId.

In theme

<control>
<name>numberColumn</name>
<property>
<name>styleClass</name>
<value>#{javascript:return StyleHelper.getNumberColumnStyleClass( this );}</value>
</property>
</control>
this refers to the column object.


SSJS code

var StyleHelper = {
// Used to calculate styleClass for a number column
getNumberColumnStyleClass: function( column ){
try {
var entry = column.getViewRowData();
var styleClass = 'numberCell';
if( entry.isCategory() ){ styleClass += ' categoryCell'; }
if( entry.isTotal() ){ styleClass += ' totalsCell'; }
return styleClass;
} catch( e ){ /* Exception handling */ }
}
}

Regular column cells will get class="numberCell".
Totals column cells will get class="numberCell totalsCell".
Category column cells will get class="numberCell categoryCell".

All I have to do to add dynamic styling to future columns is to set themeId on the column to numberCell.

Friday, August 26, 2011

XPages: Passing event handler code to custom control

Update: After a little test, it looks like the onchange event of the combo fires for every refresh. I'll move the code to beforeRenderResponse or something like that instead.

In an application I'm currently working on, I have a combobox that's used in several pages. The values the combobox contains persist over every page, but what happens when the user changes value varies from page to page.

I saw that the combobox has several properties for events under all properties. I tried adding
#{compositeData.onchange} to the onchange event, and it works. One caveat is that it seems to fire three times, but I can live with that.

To implement:
Add custom properties to the custom control for the events you want to have custom event handlers for. In the source code of the field, add attributes for the events you want code to run. E.g. onchange="#{compositeData.onchange}"

In the XPage under custom properties for the custom control, write the SSJS you want to run for your events.

That's about it.

I have only tested this with ComboBoxes, but I'm not surprised if it works for most fields that have event properties.

Tested on server running Domino 8.5.2 FP2

Friday, July 22, 2011

Update on the "enhanced" validation messages

I added functionality to select/focus a dijit tab if the field is inside a dijit.layout.TabContainer. I also added a highlight effect when a field is focused.

Source code for the custom control can be found in the original post.

Tuesday, July 19, 2011

Custom Control for "enhanced" validation messages

Update 22.06.2012: Now shows messages not bound to any control. E.g. messages related to using concurrencyMode
Update 08.06.2012: Added sorting routine to get the messages in the same order that they're in the page
Update 22.07.2011: I added functionality to select/focus a dijit tab if the field is inside a dijit.layout.TabContainer. I also added a highlight effect when a field is focused.
Disclaimer: This custom control is not entirely my idea. I've been thinking about doing something like this for a while. After I tried to help with this question by Steve Pridemore in the XPages Development Forum, I found the solution.
The code below can be used as a custom control that is a little bit more advanced (probably has its flaws) than the regular Display Errors control. If the field with a validation error has a label, it shows the label, then the error message. On the label, a link is generated that sets focus to the related field when you click it.
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
 <xp:this.beforeRenderResponse>
  <![CDATA[#{javascript:function addChildrenClientIds(component:javax.faces.component.UIComponentBase, clientIds:java.util.ArrayList) {
 try {
  var children = component.getChildren();
  
  for (var child in children) {
   clientIds.add(child.getClientId(facesContext));
   if (child.getChildCount() > 0) {
    addChildrenClientIds(child, clientIds);
   }
  }
 } catch (e) {
  /*Debug.logException(e);*/
 }
}

try {
 var messageObjects = [];
 var messageClientIds = facesContext.getClientIdsWithMessages(); 
 
 // There are messages for components - Get client ids in sorted order
 if (messageClientIds.hasNext()) {
  var clientIds = new java.util.ArrayList();
  addChildrenClientIds(view, clientIds);
 }
 
 // Used to keep track of which messages are for components
 var componentMessages = new java.util.ArrayList();
 
 while (messageClientIds.hasNext()) {
  var clientId = messageClientIds.next();
  if( !clientId ){ continue; }
  
  var component = view.findComponent( clientId.replace( view.getClientId( facesContext ), '').replace( /\:\d*\:/g, ':') );
  if (!component) { continue; }
  
  // Fetch messages for component
  var message = '',
  messages = facesContext.getMessages( clientId );
  while (messages.hasNext()) {
   var messageItem = messages.next();
   message += (message) ? ', ' : '' + messageItem.getSummary();
   
   componentMessages.push( messageItem );
  }
  
  // If component has label - fetch
  var labelComponent = getLabelFor(component);
  var label = (labelComponent) ? labelComponent.getValue() : '';
  if (!label && component) {
   var id = component.getId();
   if (id.indexOf('_') > 0) {
    label = id;
   }
  }
  
  if (label && label.indexOf(':') === -1) {
   label += ':';
  }
  
  messageObjects.push({
   index : clientIds.indexOf(clientId),
   clientId : clientId,
   label : label,
   message : message
  });
 }
 
 // Sort message object by the order of the components in the page
 messageObjects.sort(function (a, b) {
  if (a.index > b.index) { return 1; }
  if (a.index < b.index) { return -1; }
  return 0;
 });
 
 // Add all (if any) system messages at the top
 var allMessages = facesContext.getMessages();
 while( allMessages.hasNext() ){
  messageItem = allMessages.next();  
  if( !componentMessages.contains( messageItem ) ){   
   messageObjects.unshift({ message: messageItem.getSummary() });
  }
 }
 
 viewScope.messageObjects = messageObjects;
} catch (e) { 
 /*Debug.logException(e);*/
}
}]]></xp:this.beforeRenderResponse>
 <xp:scriptBlock>
  <xp:this.value><![CDATA[var EMessages = {
 // Set focus to field
 setFocus: function( clientId ){
  var matchingFieldsByName = dojo.query('[name=' + clientId + ']');
  if (matchingFieldsByName.length > 0) {
   if (dijit && dijit.registry) {
    this.showDojoTabWithField(clientId);
   }
   var field = matchingFieldsByName[0];
   
   // Workaround for dijit fields
   if( field.getAttribute( 'type' ) === 'hidden' ){
    var matchingFieldsById = dojo.query('input[id=' + clientId + ']');
    field = matchingFieldsById[0];
   }
   
   field.focus();
   dojo.animateProperty({
    duration : 800,
    node : field,
    properties : {
     backgroundColor : {
      start : '#FFFFEE',
      end : dojo.style(field, 'backgroundColor')
     }
    }
   }).play();
  }
  return false;
 },
 
 // If field is inside a dijit/extlib TabContainer - activate
 showDojoTabWithField: function( clientId ){
  dijit.registry.byClass("extlib.dijit.TabContainer").forEach(function (tabContainer) {
   dojo.forEach(tabContainer.getChildren(), function (containerPane) {
    if ( dojo.query( containerPane.containerNode ).query( '[name="' + clientId + '"]' ).length > 0) {
     tabContainer.selectChild(containerPane);
     return;
    }
   });
  });

  dijit.registry.byClass("dijit.layout.TabContainer").forEach(function( tabContainer ){
   dojo.forEach( tabContainer.getChildren(), function( containerPane ){
    if( dojo.query( containerPane.containerNode ).query( '[name=' + clientId + ']' ).length > 0 ){
     tabContainer.selectChild( containerPane );
    }
   });
  });
 }
}]]></xp:this.value>
 </xp:scriptBlock>
 <xp:repeat id="messageRepeat" styleClass="xspMessage" rows="30" value="#{viewScope.messageObjects}" var="messageObject">
  <xp:this.rendered><![CDATA[#{javascript:return ( viewScope.messageObjects && viewScope.messageObjects.length > 0 ); }]]></xp:this.rendered>
  <xp:this.facets>
   <xp:text xp:key="header" escape="false">
    <xp:this.value><![CDATA[<ul>]]></xp:this.value>
   </xp:text>
   <xp:text xp:key="footer" escape="false">
    <xp:this.value><![CDATA[</ul>]]></xp:this.value>
   </xp:text>
  </xp:this.facets>
  <li>
   <xp:panel rendered="#{!empty(messageObject.clientId)}">
    <a href="#" onclick="return EMessages.setFocus( '#{messageObject.clientId}');">
     <xp:text escape="false">
      <xp:this.value><![CDATA[#{javascript:return (messageObject.label) ? messageObject.label : messageObject.message;
}]]></xp:this.value>
     </xp:text>
    </a>
   </xp:panel>
   <xp:text value="#{messageObject.message}" rendered="#{javascript:return (messageObject.label != '');}" />
  </li>
 </xp:repeat>
</xp:view>



The code should work with fields inside a single level repeat. I'm not sure about deeper nesting. Pop the custom control into the page like you would with the Display Errors control.

Feel free to use the code however you like. If you improve on it, please share with the community.

Wednesday, July 13, 2011

XPages: Styling required and invalid fields

Just discovered that in Domino 8.5.2 (not sure about previous releases), invalid fields get the attribute aria-invalid=true, and required fields aria-required=true.

That makes it easy to style in modern browsers (>IE6).

Simply add a couple of style rules (just an example):
[aria-required=true] { background-color: #ffe; }
[aria-invalid=true] { background-color: #fee; border-color: red; }

Valid - required fields "highlighted"


Invalid


+1 to IBM for implementing :)

Share and enjoy!

Tuesday, July 12, 2011

Small tip regarding optimizing FTSearches

Lately I've been working on SQL (MS). At work today, I had a talk about optimizing queries with a colleague more seasoned in the art of writing queries. We got into a talk about if the order of the filter statements (WHEN ..=..) and performance. Apparently, MS have optimized their engine so that the order of the filtering statements don't have much influence on the performance of the query.

This got me thinking about FTSearch. A year or so ago, I thought about doing some testing on how you could structure an FT query to get the best performance, but never got around to do it.

I did a little test today, and it seems like the order of the filters doesn't influence the result much. One thing that seems to heavily influence the result is if one of the query items alone results in a lot of documents.

If you search for "Tom", and the value "Tom" is in a field in a lot of documents, this will drag down the result, no matter if another query item in the query would result if only one document being returned from the query.

Example from test:
Searching for 'abigail AND abbott' - 2-5ms to get result.
Searching for '[Form=Person] AND abigail AND abbott' - 15-20ms to get result.

Conclusion from my test. Query items in a FTSearch that alone results in a lot of documents drags down the performance of the entire query. Order of query items doesn't seem to influence the performance of the query.

If you're building a search engine for databases with a lot of documents, try to avoid having general filters (Form/etc.) if possible.

Tuesday, June 21, 2011

Tip for those working with Database events in 8.5.x designer

If you're working with Open/Close events, it's quite cumbersome when you want to test your code. You have to close the app in both Domino Designer and Notes. I used to close the designer every time I wanted to test modified code.

During a chat, Tim Tripcony mentioned that you could close the apps in Designer from the Package Explorer. In Package Explorer, each app is shown as a project. To release the app from Designer, simply right click the project, and select close project. No need to remove the app from Designer or close Designer to release the app from memory.

Share and enjoy! :)

Creating your own keyboard shortcuts in Domino Designer

File -> Preferences --> General -> Keys

I currently have two custom keyboard shortcuts, Alt + b to build a single project, and Alt + c to close a project in Package Explorer.

Friday, June 17, 2011

One editable area can be used multiple times in a Custom Control

Not sure how many now this, but an editable area (xp:callback) can be used multiple times inside a custom control.

If you have an editable area with fields, you can put the field several places in the custom control (same facetName). I'm currently working on a custom control that lets the user edit/add documents. Inside a repeat control, I show the existing documents. Below that, the user can add documents.

E.g.
..
<xp:repeat>
..
<!-- View and edit existing fields -->
<xp:callback facetName="fields" disableTheme="true" />
..
</xp:repeat>
..
<!-- Create new document -->
<xp:callback facetName="fields" disableTheme="true" />
..
I also discovered that currentDocument seems to point to the "closest" document data source.

Monday, June 13, 2011

Simple API tester - SSJS bugs remain

A couple of years ago I blogged about bugs in Array.splice, String.match with global modifier, and String.replace with function as parameter.

I did a little test on the current beta of 8.5.3. Guess what. Two years later, the bugs still haven't been fixed.

I made a small API tester utility that you can set up tests for the API/expected result. In the demoapp, there are tests for the bugs that I'm aware of. I also added a couple of tests for inconsistencies in the @-functions api.

It's quite simple to set up for JS/ECMAScript tests. Do the test in Firebug or a similar JS console. Copy/paste the expression, and the result into the fields in the XPage. Run test to see if the result is what should be expected. Note that SSJS follows ECMA-262 Edition 3. Firefox also follows this standard, but has features from JavaScript 1.8.x (according to wikipedia) that SSJS doesn't have.

>> Download API Tester

Disclaimer:
This is beta software from IBM and does not represent a commitment, promise or legal obligation by IBM to deliver, in a future release of Notes/Domino or Lotus Notes Traveler, any material, code or functionality described/shown in this presentation.

Friday, June 10, 2011

Another release of the API Inspector

Changes
* Possibility to select XPage to inspect
* No longer a custom control - One XPage to inspect them all!
* Links to the XPages API for XPages classes


You can download the latest release from OpenNTF

Screenshots


Thursday, June 9, 2011

New release of the XPages API Inspector

You can download it from OpenNTF.

Changes:
* Tweaked the user interface
* Now requires 8.5.2 or higher (Process data without validation)
* Removed dijit.form.Textarea from the expression area, and made a simple resize function instead (due to refresh issues)
* Previous expressions are stored/can be selected from a combo box
* SSJS Code and CSS moved into a library/stylesheet for easier maintenance

Screenshots



Wednesday, June 8, 2011

Enabling template inheritance per design element on new design elements

The new design elements (everything that you have to edit through Package Explorer) don't have an interface to set template inheritance on them.

You can enable this functionality with a little hack.

Create a view that shows all design elements. Set the first column formula to $Title. This makes it easier to find the design element you're looking for.

Set the $Class field on the design element document to the template you want it to inherit from. You can do this using a formula agent, or a plugin like Formul8.

That's about it. I'm not sure how the refresh task handles compiling of your java code, but since it seems to work when the design task refreshes an entire application from a template, I'm not surprised if everything works as you expect it to do.

Per design element inheritance enables you to have a central repository for things like message bundles. If you want to change a typo in a label (e.g. Adress -> Address), you can change this in the repository, and all applications that "subscribes" to this bundle will be updated with the fix.

Share and enjoy!

Tuesday, June 7, 2011

Trying to find ntrigger.dll for 64 bit server - Trigger Happy/Audit Manager

I'm trying to implement Audit Manager on a 64bit domino server.

I've hammered my friend Google with every keyword I can think of, but it doesn't look like anyone has successfully made a 64bit version of the ntrigger.dll/published it.

I managed to compile a 64bit version, but it crashes the server after it's initialized.

If anyone has managed to make a working 64bit version of the dll, I would be very happy if you could send it to me (tvaland at gmail)/contribute it to OpenNTF.

Thanks in advance :)

Monday, June 6, 2011

Simple helper function to get path to an application

I use this when generating URLs in SSJS. The reason that protocol/hostname is added is so that the generated urls work with xp:link.

If an xp:link url starts with /, it generates the path to the current application at the start of the url. If you want to link to another app/page on the current server you either have to make an html link (<a>), hard code the path, or use something like this function.

// Returns absolute path (including protocol/hostname) to the current application
// or specified [database]
function getAppPath( db:NotesDatabase ){
try {
var currentUrl = context.getUrl();
var hostname = currentUrl.getHost();
var protocol = currentUrl.getScheme();

db = db || database;
return protocol + '://' + hostname + '/' + db.getFilePath().replace( /\\/g, '/' );
} catch( e ){ /* Exception handling */ }
}

Monday, May 30, 2011

Useful sidebar widgets/plugins

I was looking for the DocViewer plugin and found a couple more plugins posted by Alan Lepofsky a couple of years ago.

https://www.socialtext.net/lotus/lotus_notes_sidebar_apps_and_plugins

Monday, May 16, 2011

Force embedded views to open on the same server as the app containing them

I've been doing some Notes development lately. I've noticed that the embedded views are being loaded from the server where the app that have the embedded views was opened last. I wrote a little procedure that you can run on PostOpen in database script.

Input parameter: the name(s) of the forms with external embedded views.

The code inspects the DXL of the form and fetches all the replicaids of the external embedded views. It then tries to open these databases and add them to the workspace with the server the user is currently on. As far as I've tested, it seems to work as described. Let me know if there are bugs.

Sub loadEmbeddedViewsOnCurrentServer( formsWithEmbedded As Variant )
On Error GoTo bubbleError
'// Goes through the form(s) specified by name in formsWithEmbedded and opens the dbs
'// that have the embedded views on the same server as the code is running
If DataType( formsWithEmbedded ) = 8 Then
formsWithEmbedded = Split( formsWithEmbedded, "¤¤¤" )
End If

Dim s As New NotesSession, db As NotesDatabase, currentServer As String
Set db = s.currentDatabase
currentServer = db.server

'// Find forms
Dim noteCol As NotesNoteCollection
Set noteCol = db.createNoteCollection( False )
noteCol.selectForms = True
noteCol.selectionFormula = |$title="| + Join( formsWithEmbedded, |":"| ) + |"|
Call noteCol.buildCollection()

Dim formNoteId As String, formDoc As NotesDocument
Dim dxlExporter As NotesDXLExporter, dxlStream As NotesStream
Dim embeddedPosition As String, embeddedView As String
Dim formDxl As String, embeddedViews As Variant, replicaid As String
Dim workspace As New NotesUIWorkspace(), embeddedViewDb As NotesDatabase
Dim openedDbs As Variant, result As Variant

openedDbs = Split( "" )
Set dxlStream = s.createStream()

formNoteId = noteCol.getFirstNoteId()
While formNoteId <> ""
Set formDoc = db.getDocumentById( formNoteId )

'// Extract DXL from form
Set dxlExporter = s.createDxlExporter( formDoc, dxlStream )
Call dxlExporter.process()

dxlStream.position = 0
formDxl = dxlStream.readText

'// Get embedded view info
embeddedPosition = InStr( formDxl, "<embeddedview" )
While embeddedPosition > 0
'// Embedded views can be defined as <embeddedview /> or <embeddedview></embeddedview> - try both
embeddedView = StrLeftBack( StrRightBack( formDxl, "<embeddedview" ), "</embeddedview>" )
If embeddedView = "" Then embeddedView = StrLeftBack( StrRightBack( formDxl, "<embeddedview" ), "/>" )

'// Open databases
If InStr( embeddedView, "database" ) > 0 Then
replicaid = Strtoken( StrRightBack( embeddedview, "database='" ), "'", 1 )
If replicaid <> "" Then
'// If db hasn't been opened before in the script - open
If IsNull( ArrayGetIndex( openedDbs, replicaid ) ) Then
Set embeddedViewDb = New NotesDatabase( "", "" )
Call embeddedViewDb.openByReplicaId( currentServer, replicaid )
If Not embeddedViewDb Is Nothing Then
If embeddedViewDb.isOpen Then
Call workspace.addDatabase( currentServer, embeddedViewDb.filePath )
End If
End If

openedDbs = ArrayAppend( openedDbs, replicaid )
End If
End If
End If

'// Remove start tag for the processed embedded view - only run once per embedded
formDxl = Replace( formDxl, "<embeddedview" + embeddedView, "" )

'// Find next embedded view
embeddedPosition = InStr( formDxl, "<embeddedview" )
Wend

Call dxlStream.truncate()
formNoteId = noteCol.getNextNoteId( formNoteId )
Wend

Exit Sub
bubbleError:
Error Err, Error
End Sub

Thursday, April 14, 2011

Simple trick to format XPages Checkbox group

If you put display:inline on a table (which XPages generates for a CheckboxGroup), you can style each cell like an inline element.

To make a four column checkbox group (put fourColumnCheckboxGroup as styleClass on the xp:checkBoxGroup), put this in your CSS file:
.fourColumnCheckboxGroup { display: inline; }
.fourColumnCheckboxGroup td { float: left; width: 24%; }

For three columns, use 32% and so on. I use (100 / numberOfColumns) - 1 as that seems to work best cross browser.

Share and enjoy :)

Tuesday, April 5, 2011

Add a custom footer to a ViewPanel column

Open the XPage in source mode. Set the pointer inside <xp:this.facets>. Create a panel, div, output text or whatever with the attribute xp:key="footer", and the content will be showed at the bottom of the column.

E.g.
<xp:text value="#{viewScope.totalsForSomeColumn}" xp:key="footer" />

Monday, March 28, 2011

Small bugfix in the CKEditor integration for XPages demoapp

When you tried to upload more than one file at once (e.g. one attachment and an image), only the first file uploaded successfully.

This bug has now been fixed.

The Domino part of the demo requires Dojo 1.4.1 (Domino 8.5.2 server), as it's hard coded in the HTML header. Modify it to suit your environment.

>> Download demoapp

Thursday, March 3, 2011

Repeat Controls and Multivalue fields

There was a small discussion over at Julian's blog regarding multivalue fields and repeat controls. I knew that it was possible to bind Javascript objects and arrays to input fields inside a repeat control using EL syntax.

I got a little curious, and decided to test if the same was possible for a multivalue field. It turns out it's just as easy.

Instead of my regular demoapps, I'll post source code for the test XPage I made. It's bound to a form with one field, named field (don't name any field field in real applications!!).

To test it, create a form named Test with a field named field. Create an XPage/paste the source code from the file below. Click the add-button to add an item in the multivalue field. Save the document to test if it works. Not sure how many versions back this works with.

Demo XPage source code

Wednesday, March 2, 2011

RepeatControls troubles - solution repeatControls

I've struggled more or less a working day trying to figure out why all of the properties I passed to a Custom Control inside a Repeat Control got nullified.

In afterPageLoad, in the Custom Control, I tried binding the compositeData values to a private variable for later use. I tried a lot of ways of making the properties survive, but whatever I tried, they ended up as null values before I could get a hold of them.

Components inside the control could "read" the composite data, but when the life cycle got as far as scripting, the values were nullified. The solution it turns out is setting the property repeatControls on the Repeat Control to true. I guess this results in a less agressive trash collector(?).

Anyways, my recommendation to IBM is to default set the option to true, unless the performance hit is big. I bet I'm not the only developer that has struggled/will struggle with this strange behaviour.

Monday, February 14, 2011

Small update on the EventDelegator demo - onChange implemented

I don't know if anyone is using my EventDelegation technique for custom events.

In case any of you do, I've updated the demo/script in the demoapp. You can now also delegate onChange events.

Thursday, February 10, 2011

XPages: dojo performance tip

This tip is most valid if there's a big difference between edit mode and read mode.

You can compute if dojo modules are rendered the same way you compute any other component.
E.g. in this.resources (source mode):
<xp:dojoModule name="dijit.Editor" rendered="#{javascript:return currentDocument.isEditable();}" />
In one of my XPages the read mode has 17 requests, and the edit mode has 56 requests.

I don't notice much difference, as I'm on a decent line with low latency, but for users with low bandwith/high latency the difference can probably be highly noticable.

XPages: RichTextEditor + custom partial refresh + validation error = lost content

I ran into a problem today with content disappearing from a rich text editor when there was a validation error/saving. I did a little investigation, and it turns out that the RichTextEditor has a submitListener that updates a hidden field. This hidden field is used by the server to set the field/scope value on the server (at least it looks that way).

When my custom partial refresh ran, the hidden field didn't have a value. When combined with a validation error, this resulted in an empy RichTextEditor after refresh.

To work around this problem, I run the code (CSJS) below before running any "custom" partial refreshes:
// Process any autogenerated submit listeners
if( XSP._processListeners ){ // Not sure if this is valid in all versions of XPages
 XSP._processListeners( XSP.querySubmitListeners, document.forms[0].id );
}
This code won't work if you have multiple forms. Then you'd have to write a loop that calls the method once per form.

Thursday, February 3, 2011

Domino Designer setting that cleans up whitespace in XPage source


(Clear all blank lines)

Press Ctrl+Shift+F to autoformat code. With the above setting in place, you also should get rid of blank lines.

Share and enjoy!

Monday, January 31, 2011

LS: Currency data type seems to return proper results in arithmetics

I had a problem the other day with JS' inaccurate arithmetics. One can also stumble onto the same problem with LS:
Dim first As Double, sec As Double, third As Double
first = 0.1
sec = 0.2
third = 0.3

Print ( first + sec ) = third
'// -> False

I got a little bit curious, and lurked around in the documentation for other number data types in LS. Lo and behold, there's Currency. It seems to have some inbuilt functionality that corrects the inaccuracy in data types like Double.
Dim first As Currency, sec As Currency, third As Currency
first = 0.1
sec = 0.2
third = 0.3

Print ( first + sec ) = third
'// -> True
Nice to know if you need accurate results when doing arithmetics in LS.

Friday, January 28, 2011

CKEditor: Using external plugins from an NSF

This is a follow-up to my previous blogpost regarding importing files to an NSF.

If you want to use CKEditor plugins from an NSF, you have to create an override-function for CKEditor.getUrl. The reason for this is that CKEditor automatically adds a timestamp-parameter to the url, which Domino doesn't like.

CKEDITOR_GETURL = function( resource ){
// From NSF - don't alter the url
if( resource.indexOf( '.nsf' ) > -1 ){
return resource;
} else { // Let CKEditor handle the url
return null;
}
}


This function has to be loaded before ckeditor.js.

Using the Package Explorer to import file resources to an NSF

Using the package explorer, you can add import files/folder from your file system.

To import folders from the file system: In (windows) explorer, select the file(s)/folder(s) you want into the NSF, and copy them (CTRL+C or right click/copy). Then in Package Explorer, select the WebContent folder, and paste (CTRL+V or right click/paste).

It's as simple as that :)

If you're wondering what I needed this for, it's to create/maintain CKEditor plugins locally in an NSF. You can import "external" plugins using CKEDITOR.plugins.addExternal.

The imported files are noticed by the Design task, so this also works with templates.

Tuesday, January 18, 2011

XPages: Small update to the search interface demoapp

I've added automatic update of the FTIndex on search, when content has changed. There are rumours(?) on the web that FTSearch is evil, and that changes takes a long time to get into the index, even when running NotesDatabase.updateFTIndex.

I have never had those problems. It might be that it was something that was fixed in a previous version of the Domino server. I don't know.

To test how it works, change a document using the Notes client, then search for the changed value in the XPage.

Original post about the search interface demo

>> Download demoapp

Tuesday, January 11, 2011

XPages: Three ways to build a search interface revisited

I decided to update the search interface demo a little bit.

Changes:
* Cleaned up/improved some of the code
* Made the ViewEntry search sortable in both directions
* Improved the ViewEntry search code, so that it should be a little quicker
* Added a load time indicator (measures the time it takes to search/sort)
* When you click to sort a column, the pager automatically goes to the first page
* Changed resorting to execute partially (partial refresh)
* Added error handling (shows up as a "validation error" box)

The app has >40k documents, so you should be able to get an impression of the performance on bigger datasets. Remember to create FT Index in the app, or it won't work. It might take some time due to the number of documents.

>> Download demoapp