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
Wednesday, September 28, 2011
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)
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 greyTo use the code snippet below, you also need the partial event hijackervar 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();
}
}
Labels:
code snippet,
xpages
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.
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.
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.
Labels:
random tip,
themes,
xpages
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
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
Labels:
custom control,
xpages
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.
Source code for the custom control can be found in the original post.
Labels:
xpages
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.

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.
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.
Labels:
code snippet,
xpages