Tuesday, May 21, 2013

Custom Control for custom layout of checkboxes

In a recent project, I wanted to create an interface to toggle different settings for different user types in an app. To save real estate on the page and (hopefully) make it more intuitive, I wanted to have a column per user type, and organize the checkboxes vertically.

I couldn't find any way to do this out of the box. To make it work, I made a regular checkbox group per field. Then in a repeat control, I have regular HTML checkboxes with name fetched from the real checkbox group. The checkbox group has to have the same values as the HTML checkboxes, or the values will be discarded.

The checkbox component is removed from the DOM via javascript when the page loads. If the real checkbox component isn't removed, you will only be able to select, and not deselect.

Screenshot from the demoapp (each checkbox column is bound to a multivalue field):


The custom control has four properties:
  • checkboxValues: Array of strings - Values for the checkboxes
  • fieldNames: Array of strings - Names of the fields in the form you want to connect the checkboxes to (remember to set them to multi-value in form)
  • headerLabels: Array of strings - Header labels for the checkbox columns
  • rowLabels: Array of strings - Labels for the checkbox rows

Currently, the custom control uses currentDocument to bind the fields. If the control doesn't work, you might need to use a more specific document data source.

Example usage
<xc:CheckboxTable checkboxValues="#{javascript:return ['inbox','outbox'];}"
   fieldNames="#{javascript:return ['hidden_items_administrators','hidden_items_users','hidden_items_limited_users'];}"
   headerLabels="#{javascript:return [labels.administrators, labels.users, labels.limitedUsers];}" rowLabels="#{javascript:return [labels.inbox,labels.outbox];}" />
The demoapp has been tested/works in these browsers: IE8/9 (should work in earlier browsers, but the layout is ugly), latest versions of Chrome, Firefox and Opera.

>> Download demoapp

The technique I used (real+"fake" checkboxes) should enable you to customize the layout of checkboxes however you want. I imagine the technique should also work for radio buttons.

Wednesday, March 20, 2013

Fix for partial refresh on Dojo Tab Container/Content Pane

I wrote this a while back, but I couldn't find that I'd shared it.

You need to use the partial refresh hijacker to use the code snippet.

This code snippet initializes Dojo Tab Containers/Dojo Content Panes in the area that's refreshed:
// Fix problem with partial refresh on Dojo Tab Container/Content pane
// Source for inspiration:
// http://www.openntf.org/projects/pmt.nsf/0/D228115FAA98DDEC86257A7D0050E7FF
dojo.subscribe( 'partialrefresh-complete', this, function( method, form, refreshId ) {
 var tabContainersAndContentPanes = dojo.query( '[id=' + refreshId + '] .dijitTabContainer[widgetid], ' +
  '[id=' + refreshId + '] .dijitContentPane[widgetid]' );
 if( tabContainersAndContentPanes.length === 0 ) {
  return;
 }
 
 for( var i = 0; i < tabContainersAndContentPanes.length; i++ ) {
  var widgetId = tabContainersAndContentPanes[i].getAttribute( 'widgetid' );
  var widget = dijit.byId( widgetId );
  if( widget ) {
   widget.startup();
   widget.resize();
  }
 }
} );

Add the code snippet in a dojo.addOnLoad function or something similar.

If you have other widget types that fails to initialize, just modify the selector/startup commands to fix.

Friday, January 18, 2013

Small LS class that can be used to check if fields have changed

We've had some issues with semaphore locks on one of our import databases. The import database has routines that import/update data, then replicate it to a cluster when it's done.

We're not exactly sure what triggers the locks, but the server crashed sometimes several times a day, so we decided to see if the import routines could be optimized to do as few writes as possible.

Several of the routines saved documents even if there were no field changes. I wrote a simple class to test for field value changes.
'// Used to test if certain fields have changed
Class FieldChangeChecker
 Private fieldsToCheck As Variant
 Private fieldValues List As Variant
 
 '// Run this after calculcation/etc. to see if specified fields have changed
 Function haveFieldsChanged( doc As NotesDocument ) As Boolean
  On Error GoTo bubbleError
  
  If doc.isNewNote Then
   haveFieldsChanged = True
   Exit Function
  End If
  
  Dim initialValue As Variant, currentValue As Variant
  ForAll fieldName In Me.fieldsToCheck
   initialValue = Me.fieldValues( fieldName )
   currentValue = doc.getItemValue( fieldName )(0)
   
   If initialValue <> currentValue Then
    haveFieldsChanged = True
    Exit Function
   End If
  End ForAll
  
  Exit Function
bubbleError:
  Error Err, errorMessage()
 End Function
 
 '// Comma separate field names. E.g. "forename,surname"
 Sub New( ByVal commaSeparatedFieldNames As String )
  On Error GoTo bubbleError
  
  Me.fieldsToCheck = FullTrim( Split( commaSeparatedFieldNames, "," ) )
  
  Exit Sub
bubbleError:
  Error Err, errorMessage()
 End Sub
 
 
 '// Run this before calculation/etc.
 Sub readInitialValues( doc As NotesDocument )
  On Error GoTo bubbleError
  
  ForAll fieldName In Me.fieldsToCheck
   Me.fieldValues( fieldName ) = doc.getItemValue( fieldName )(0)
  End ForAll

  Exit Sub
bubbleError:
  Error Err, errorMessage()
 End Sub 
End Class
Example of use:
..
Dim fieldChangeChecker As New FieldChangeChecker( "FirstName,LastName,Address,PostCode,City" )
Set customerDoc = customersView.getFirstDocument()
While Not customerDoc Is Nothing
 Call fieldChangeChecker.readInitialValues( customerDoc )

 '// Code that looks up the newest information and sets fields

 If fieldChangeChecker.haveFieldsChanged( customerDoc ) Then
  Call dataDoc.Save(True, False) 
 End If

 Set customerDoc = customersView.getNextDocument( customerDoc )
Wend
..
Regarding the errorMessage-function in the class. I use error bubbling in all my LS code. If something fails somewhere in a routine, I want the routine to stop executing. At the top of the stack, I use OpenLog/LogError. This gives me a nice pseudo stack of the function calls.
Code for errorMessage function:
Function errorMessage As String
 '// Simple function to generate more readable errors when dealing with error-bubbling
 Dim message As String
 message = Error
 
 If CStr( GetThreadInfo(10) ) = "INITIALIZE" Then
  errorMessage =  "Error " & Err & " on line " & Erl & " in function " & GetThreadInfo( 10 ) & ": " + Error
 Else
  errorMessage =  Chr(13) + Chr(9) + "Error " & Err & " on line " & Erl & " in function " & GetThreadInfo( 10 ) & ": " + Error$ 
 End If 
End Function

Friday, October 12, 2012

Code Snippet - Close dialog if all fields are valid

Today I was working on a dialog that had fields with validation. I only want to close the dialog if all fields are valid. I'm not aware of any inbuilt XSP methods that does this. This code snippet checks for any invalid fields in the dialog. If all fields are valid, the dialog is closed.
function closeDialogIfValid( dialogId ){
 var invalidCount = dojo.query( '[id="' + dialogId + '"] [aria-invalid="true"]' ).length;
 if( invalidCount === 0 ){
  XSP.closeDialog( dialogId );
 }
}
This probably only works if you're using server side validation.

If there is something like this in the XSP API, please let me know.

Share and enjoy!

Thursday, September 6, 2012

Snippet to clear session for user

During testing, I sometimes log in as different user to test hide/whens/etc. I used to delete the SessionID cookie in the browser to clear session scoped beans/sessionScope variables. Today, I looked for a solution to automate this. This line will clear all objects related to a session:
facesContext.getExternalContext().getSession( false ).invalidate();
If you want to clear session when logged in user changes for the current "XPages" session, here's the snippet I use (put the code in afterPageLoad or beforePageLoad):
// Reset session when user changes
var currentUserName = sessionScope.currentUserName;
var userName = session.getEffectiveUserName();
if( currentUserName && userName !== currentUserName ){
 facesContext.getExternalContext().getSession( false ).invalidate();
}   
sessionScope.currentUserName = userName;
I would think this is better than deleting the cookie, as the server is immediately notified that it should flush the objects bound to the session. When I delete the cookie, I believe that the objects are kept in memory until a specified timeout.

Monday, August 20, 2012

Comprehensive guide to Design Definitions

I found this guide in IBM's Application Development wiki while looking for information on Design Definitions:
Native and Custom Control Custom Visualization Best Practices

So far it's the most comprehensive guide to Design Definitions I've found.