Reuse web agents that PRINT to the browser in XPages
When upgrading classic Domino applications to XPages one particular problem arises constantly: " what to do with the PRINT statements in existing agents that write back directly to the browser?" Currently there is no automatic way to capture this output.
However with a little refactoring of the agent the output can be recycled. You can use a computed field for the result showing it on a page that maintains the all over layout of your new application or use the XAgent approach to replace the whole screen (I'm not discussing the merits of that here). These are the steps:
Update: (thx Tim): Altered the code to work when the agent doesn't print anything
Update: To successfully run such an agent it needs to have the property set "Run as web user", otherwise you get an error. Also you need to design your own handover of the document if you want to manipulate that.
As usual: YMMV
However with a little refactoring of the agent the output can be recycled. You can use a computed field for the result showing it on a page that maintains the all over layout of your new application or use the XAgent approach to replace the whole screen (I'm not discussing the merits of that here). These are the steps:
- Make sure your agent is set to "Run as web user" in the agent properties
- Add the
AgentSupport
LotusScript library to your agent - Initialize the ResultHandler class:
Dim result as ResultHandler
that will take in the print statements
SET result = new ResultHandler - Use Search & Replace in your agent and replace
Print
withresult.prn
- Add at the end
call result.save()
- In your XPage or CustomControl add the
AgentSupportX
SSJS library - Get the result with a call to
agentResult("[name-of-your-agent]")
. You can process it further or display in in a computed field etc.
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<xp:this.resources>
<xp:script src="/AgentSupportX.jss" clientSide="false"></xp:script>
</xp:this.resources>
This comes from an agent:
<xp:text escape="false" id="computedField1">
<xp:this.value> <![CDATA[#{javascript:agentResult("SampleAgent");}> </xp:this.value>
</xp:text>
</xp:view>
The sample agent like this:
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<xp:this.resources>
<xp:script src="/AgentSupportX.jss" clientSide="false"></xp:script>
</xp:this.resources>
This comes from an agent:
<xp:text escape="false" id="computedField1">
<xp:this.value> <![CDATA[#{javascript:agentResult("SampleAgent");}> </xp:this.value>
</xp:text>
</xp:view>
Option Public
Option Declare
Use "AgentSupport"
Dim result As ResultHandler
Sub Initialize
Set result = New ResultHandler
result. prt "Some message"
result. prt "<h1> a header </h1>"
Call result. save ( )
End Sub
Of course the interesting part are the two script libraries.The LotusScript library looks like this:
Option Declare
Use "AgentSupport"
Dim result As ResultHandler
Sub Initialize
Set result = New ResultHandler
result. prt "Some message"
result. prt "<h1> a header </h1>"
Call result. save ( )
End Sub
Update: (thx Tim): Altered the code to work when the agent doesn't print anything
%REM
Library AgentSupport
Created Mar 16, 2012 by Stephan H Wissel/Singapore/IBM
Description: Routines to improve agent calling from XPages
%END REM
Option Public
Option Declare
%REM
Class ResultHandler
Description: Capture all the print statements and feed them back
%END REM
Public Class ResultHandler
Private out As NotesStream 'This is where the result goes
Private m_fieldName As String 'The field name for the result
Private m_noPrintMessage as String 'The message if the agent didn't print anything
Private m_printResult as Boolean 'If the agent is also used classic we need the print output
Private s As NotesSession
Private doc As NotesDocument 'The document handed over by XPages
%REM
Sub new
Description: Initialize the variables
%END REM
Public Sub New
Set me. s = New NotesSession
Set me. doc = s. Documentcontext
me. m_fieldName = "AgentPrintOutputResult"
me. m_noPrintMessage = "[NoPrintOutput]"
me. m_printResult = False
End Sub
%REM
Sub prt
Description: Replacement for the print command in web agents
%END REM
Public Sub prt (msg As String )
If out Is Nothing Then
Set out = s. Createstream ( )
End if
Call out. Writetext (msg, EOL_PLATFORM )
If me. m_printResult Then
Print msg
End If
End Sub
%REM
Property Set fieldName
Description: ability to overwrite the field name
%END REM
Public Property Set fieldName As String
me. m_fieldName = fieldName
End Property
%REM
Property Set noPrintMessage
Description: ability to overwrite the message if nothing was printed
%END REM
Public Property Set noPrintMessage As String
me. m_noPrintMessage = noPrintMessage
End Property
%REM
Property Set printResult
Description: If the agent is used in classic, we need to print
%END REM
Public Property Set printResult As Boolean
me. printResult = printResult
End Property
%REM
Sub save
Description: Saves the output back into the document
One could consider naming it "Delete" so it runs automatically on object deletion
%END REM
Public Sub save
Dim mimeFlag As Boolean
Dim mimeEntry As NotesMIMEEntity
'We need to make sure we don't have the item in the document
If me. doc. Hasitem ( me. m_fieldName ) Then
Call me. doc. Removeitem ( me. m_fieldName )
End If
'Set mime conversion to false - we want native
mimeFlag = me. s. Convertmime
me. s. Convertmime = False
'There might not be a print output
If me. out Is Nothing then
Call me. agentDidNotPrintAnything ( )
End If
'Now write out
me. out. Position = 0 'Make sure we write everything
Set mimeEntry = doc. Createmimeentity ( me. m_fieldName )
Call mimeEntry. Setcontentfromtext ( me. out, "text/plain;charset=UTF-8" ,ENC_NONE )
Call me. doc. Closemimeentities ( true, me. m_fieldName )
'Close stream
Call me. out. Close ( )
Set me. out = Nothing 'If we call it again in prt it gets reinitialized
'Restore mime conversion to what it was
me. s. Convertmime = mimeFlag
End Sub
Private Sub agentDidNotPrintAnything
Set out = s. Createstream ( )
Call out. Writetext ( me. m_noPrintMessage, EOL_PLATFORM )
End Sub
End Class
The SSJS library looks like this:
Library AgentSupport
Created Mar 16, 2012 by Stephan H Wissel/Singapore/IBM
Description: Routines to improve agent calling from XPages
%END REM
Option Public
Option Declare
%REM
Class ResultHandler
Description: Capture all the print statements and feed them back
%END REM
Public Class ResultHandler
Private out As NotesStream 'This is where the result goes
Private m_fieldName As String 'The field name for the result
Private m_noPrintMessage as String 'The message if the agent didn't print anything
Private m_printResult as Boolean 'If the agent is also used classic we need the print output
Private s As NotesSession
Private doc As NotesDocument 'The document handed over by XPages
%REM
Sub new
Description: Initialize the variables
%END REM
Public Sub New
Set me. s = New NotesSession
Set me. doc = s. Documentcontext
me. m_fieldName = "AgentPrintOutputResult"
me. m_noPrintMessage = "[NoPrintOutput]"
me. m_printResult = False
End Sub
%REM
Sub prt
Description: Replacement for the print command in web agents
%END REM
Public Sub prt (msg As String )
If out Is Nothing Then
Set out = s. Createstream ( )
End if
Call out. Writetext (msg, EOL_PLATFORM )
If me. m_printResult Then
Print msg
End If
End Sub
%REM
Property Set fieldName
Description: ability to overwrite the field name
%END REM
Public Property Set fieldName As String
me. m_fieldName = fieldName
End Property
%REM
Property Set noPrintMessage
Description: ability to overwrite the message if nothing was printed
%END REM
Public Property Set noPrintMessage As String
me. m_noPrintMessage = noPrintMessage
End Property
%REM
Property Set printResult
Description: If the agent is used in classic, we need to print
%END REM
Public Property Set printResult As Boolean
me. printResult = printResult
End Property
%REM
Sub save
Description: Saves the output back into the document
One could consider naming it "Delete" so it runs automatically on object deletion
%END REM
Public Sub save
Dim mimeFlag As Boolean
Dim mimeEntry As NotesMIMEEntity
'We need to make sure we don't have the item in the document
If me. doc. Hasitem ( me. m_fieldName ) Then
Call me. doc. Removeitem ( me. m_fieldName )
End If
'Set mime conversion to false - we want native
mimeFlag = me. s. Convertmime
me. s. Convertmime = False
'There might not be a print output
If me. out Is Nothing then
Call me. agentDidNotPrintAnything ( )
End If
'Now write out
me. out. Position = 0 'Make sure we write everything
Set mimeEntry = doc. Createmimeentity ( me. m_fieldName )
Call mimeEntry. Setcontentfromtext ( me. out, "text/plain;charset=UTF-8" ,ENC_NONE )
Call me. doc. Closemimeentities ( true, me. m_fieldName )
'Close stream
Call me. out. Close ( )
Set me. out = Nothing 'If we call it again in prt it gets reinitialized
'Restore mime conversion to what it was
me. s. Convertmime = mimeFlag
End Sub
Private Sub agentDidNotPrintAnything
Set out = s. Createstream ( )
Call out. Writetext ( me. m_noPrintMessage, EOL_PLATFORM )
End Sub
End Class
function agentResult (agentName ) {
var doc = database. createDocument ( ) ;
var agent = database. getAgent (agentName ) ;
// Here is the run
try {
agent. runWithDocumentContext (doc ) ;
} catch (e ) {
return e. message ;
}
var out = session. createStream ( ) ;
var mimeEntry = doc. getMIMEEntity ( "AgentPrintOutputResult" ) ;
mimeEntry. getContentAsText (out ) ;
out. setPosition ( 0 ) ;
var result = out. readText ( ) ;
//Cleanup
mimeEntry. recycle ( ) ;
doc. recycle ( ) ;
out. recycle ( ) ;
agent. recycle ( ) ;
return result ;
}
I'm using MIME as intermediate storage format as pioneered by Tim Tripcony, so I don't need to worry about content length. You might need to play with the encoding/decoding mechanism. I haven't checked the edge cases yet.
var doc = database. createDocument ( ) ;
var agent = database. getAgent (agentName ) ;
// Here is the run
try {
agent. runWithDocumentContext (doc ) ;
} catch (e ) {
return e. message ;
}
var out = session. createStream ( ) ;
var mimeEntry = doc. getMIMEEntity ( "AgentPrintOutputResult" ) ;
mimeEntry. getContentAsText (out ) ;
out. setPosition ( 0 ) ;
var result = out. readText ( ) ;
//Cleanup
mimeEntry. recycle ( ) ;
doc. recycle ( ) ;
out. recycle ( ) ;
agent. recycle ( ) ;
return result ;
}
Update: To successfully run such an agent it needs to have the property set "Run as web user", otherwise you get an error. Also you need to design your own handover of the document if you want to manipulate that.
As usual: YMMV
Posted by Stephan H Wissel on 16 March 2012 | Comments (4) | categories: XPages