Run a Web agent on any JavaScript event

Have you ever wanted to run an agent on the Web without having the screen reload? Here is a great solution.

View member feedback to this tip.

Have you ever wanted to run an agent on the Web without having the screen reload? Are you frustrated because you'd like an agent to run on an event other than a document opening or saving? Here is a great solution for Internet Explorer 5.0+ (Other browsers now also supported, see User Feedback section below). It allows you to receive information from a LotusScript or Java agent through JavaScript. I used this code to run an agent on the "onChange" event of a dropdown box to populate a different field.

The basic idea is:

  1. Generate XML of the variables you need to use in the agent using JavaScript (supplied below).
  2. Call the agent using JavaScript (supplied below).
  3. Use the agent response for whatever you would like (alerting to the user, populating a field, etc.).

Place these two JavaScript functions in the JS Header of your form.

These JavaScript functions are used to generate an XML request and call the agent:

// Builds the XML variables
 that will be sent to the agent.
function BuildXMLVariables()
{
var objDOM = new ActiveXObject
("Microsoft.XMLDOM")

RootEl = objDOM.createNode(1, 
"RootElement", "")
objDOM.documentElement = RootEl

// add as many of these nodes as 
you need. 
// these variables will be accessible 
via the agent.
objHeaders = objDOM.createNode
(1, "DocID", "")
objHeaders.text = document.all.
DocumentID.value
RootEl.appendChild(objHeaders)

objHeaders = objDOM.createNode
(1, "Status", "")
objHeaders.text = document.all.
Status.value
RootEl.appendChild(objHeaders)

return objDOM;
}

// Calls the Agent
function runAgent(strAgentName,
 XMLvariables) {

objHTTP = new ActiveXObject
("Microsoft.XMLHTTP");

strUrl = "/" + document.all.pathName.value 
+ "/"+strAgentName+"?
OpenAgent";

objHTTP.open("POST", strUrl, false, "", "");

objHTTP.setRequestHeader("Content-type",
 "application/x-www-form-
urlencoded");

objHTTP.send(strXMLvariables);

resp = objHTTP.responseText;

objHTTP = null;

return resp;
}

Place the following line in your code to call the functions above.
Note: You can do this on ANY JavaScript event, such as onChange of a field or onClick of a button.

result = runAgent("AgentName", 
BuildXMLVariables()); 
// The result variable holds the 
response from the agent. 
// You can do anything you'd like 
with the information received from the 
agent. This example alerts the user.
alert (result);

Here are a few tips when writing the agents that will be used with this technique.

The agent should use the Print command to output the response. Prior to outputting the data, ensure the content type of the output is going to be text by putting the following line of code in the LotusScript agent.

Print |Content-type:text|

The following LotusScript sample code demonstrates how to grab variables from the XML that was submitted to the agent via the JavaScript.

Set objDoc = CreateObject
("Microsoft.XMLDOM")
objDoc.async = False
objDoc.validateOnParse = False

objDoc.loadXML 
ndocThisDocument.Request_Content(0)
txtDocumentID = 
objDoc.getElementsByTagName
("DocID")(0).text
txtStatus = objDoc.
getElementsByTagName("Status")(0).text

Do whatever processing you want and then print out the result. The result will become a JavaScript variable on the Web.

This demo code simply sends the txtStatus field information back to the browser using the LotusScript line:

Print txtStatus

This method allows you to quickly and easily get information from an agent to the browser without relying on a browser refresh, or the WebQuerySave/WebQueryOpen events. It is extremely helpful!

MEMBER FEEDBACK TO THIS TIP

For an Internet Explorer only solution this tip is good. Users should note that the agent called will be unsecured, meaning it needs to allow anonymous access. So, be careful with what data is returned.

—Bruce L.

******************************************

I don't allow anonymous access to the agent that I call using this method. The user must be logged in to the database.

—Luke Leonhard, tip author

******************************************

Some time ago I read and tried a code form Lotus SandBox. It was something like, "HTML: lesson we learn from iNotes." They use a similar technique but use iFrame or "DataIsland" XML tags and provide some JavaScript classes to use it with. The problem with that solution was the asynchronous nature of the solution. There was a lot of set Timeout (look in iNotes-WebAccess and you will find this same technique).

Is your solution "syncron?" Is the JavaScript engine waiting for the HTTP response when you invoke the send method or when you request the response text?

I would like to return an XML (content type text/XML) in the JavaScript. Can I populate another XML domain where I can easily find structured information without the need for implementing a proprietary protocol with the agent?

— Amigoni A.

******************************************

In terms of your question regarding "syncron" and "is the JavaScript engine waiting for the HTTP response," the answer is yes.

I had to double check with my database because my agent was relatively simple. I changed the agent to have a few embedded loops that did string manipulation (which is an easy way to make an agent take a long time to run). The result worked great. The browser "pauses" the JavaScript engine, waiting for the response from the server. This is most likely a relief to you -- no more setTimeout scripts. The one negative side effect of this would be that if you were running extremely complicated agents, the user would see the browser "pause" for a moment. Depending on server and connection speed, you may have to gauge showing a "Processing" div. The code for that is below.

I'm a little confused about what you're asking for in the second part of your question, "Can I populate another XML domain where I can easily find structured information without the need for implementing a proprietary protocol?" If you mean, "Can the agent send back XML?" I would say yes. To do so, I would simply write out the XML you wish to return in the agent. For example:

strReturnXML = "" 
strReturnXML = strReturnXML +
 "<note>" 
strReturnXML = strReturnXML + 
"<to>Beth</to>" 
strReturnXML =  strReturnXML +
 "<from>Luke</from>" 
strReturnXML = strReturnXML + 
"<heading>Good Luck</heading>" 
strReturnXML = strReturnXML + 
"<body>You may win an 
iPod for this tip!</body>" 
strReturnXML = strReturnXML + 
 "</note>" 
Print strReturnXML 

Then you'd have a string representation of the XML in JavaScript. If you'd like to play around with the XML in JavaScript, use:

var xmlDoc = new ActiveXObject
("Microsoft.XMLDOM") 
xmlDoc.async="false" 
returnedText = runAgent("AgentName", 
BuildXMLVariables()); 
xmlDoc.loadXML(returnedText)

—Luke Leonhard, tip author

******************************************

I'm having problems with this tip because we have SSO enabled in the server, and the response always returns to the login page. Any ideas how to resolve this?

—Werther V.

******************************************

We also have SSO on the server using this code, so I'm not sure if that is the issue. I have three questions for you though:

  1. Are you calling the agent from the same server and domain as the page that you are already authenticated to? For example, if I access a form via the URL http://www.thisdomain.com/web/myapp.nsf/MyForm?OpenForm, am I sure to have the agent URL be either relative, such as /ThisAgent?OpenAgent, or a full path with the same domain, such as http://www.thisdomain.com/web/myapp.nsf/ThisAgent?OpenAgent? By doing so, the Domino server and the Internet browser should use the same session (avoiding the login page).

  2. Are your browser security settings set to normal? Do you allow caching of login information? Internet Explorer does this by default. You can actually see the behavior if you go to any Domino application, and then navigate to another design element in the application. You are not prompted for a password every time a new page is displayed.

  3. Was the user forced to authenticate prior to this particular form loading in the database? I can see the issue arising if, for example, a user accessed a page anonymously and then did the action that resulted in JavaScript triggering the agent. The agent may require authentication, thus giving the response of the login page.

One of these could be your problem. If it is not, please reply, and I will work through some more possibilities. I'm sorry that you've had trouble implementing my tip -- I hope that this resolves it. I'll keep working to help you solve any issues.

—Luke Leonhard, tip author

******************************************

I have a problem with this code; it fails on this line in the agent:

  
objDoc.loadXML ndocThisDocument.
Request_Content(0) 

As far as I can tell it doesn't know what the ndocThisDocument object is. I'm running on Domino 6.5 on a Windows 2000 server. Any chance of explaining what this is doing wrong? I notice that usually the loadXML command either loads a string or parses a URL, so this is new ground for me!

Apart from that, I can successfully invoke the agent and have modified it to it pass parameters as part of the URL string so I can still access the values (e.g. http://….? OpenAgent&currUser=Bill∂No=123456). But I'd really like to get this working properly and not use this workaround. Do you have any ideas?

—Ralph B.

******************************************

Thanks for the positive feedback regarding the tip. I'm really glad you found good use of it. The line that the code is failing on is due to my assumption that ndocThisDocument is actually the document context of the ?OpenAgent URL... That sounds more complicated than it is. Simply add the following declarations to your agent, and you should be set!

        Dim nsThisSession As 
NotesSession 
        Dim ndbThisDatabase As 
NotesDatabase 
        Dim ndocThisDocument As 
NotesDocument 
        
        Set nsThisSession = 
New NotesSession 
        Set ndbThisDatabase = 
nsThisSession.CurrentDatabase         
        Set ndocThisDocument = 
nsThisSession.DocumentContext 

Thanks much for the question. If you have any other questions regarding my tip (or if this doesn't solve your problem), please let me know. I see my rating slowly dropping, so I'd be happy to do whatever I can to get some positive feedback. Please send the tip to your friends!

—Luke Leonhard, tip author

******************************************

This tip is excellent. I'm trying to use it as a solution to a potential problem. I have a few questions:

  1. Is there any limit to the size of XML data that can be passed to the agent from the JavaScript?
  2. Another issue is that I have to wait for the HTTP response before I can send a new HTTP request. But I have to send HTTP requests to multiple servers and I want all of the requests to be processed in parallel. Is it possible using this approach? Please keep in mind that I'll send one HTTP request per server.

Your advice will be very helpful.

—Smarjit S.

******************************************

Great questions!

Answer to your 1st question:
I have not hit a limit of XML data using this method, and in some cases I pass full views of information, so I don't believe that limits will cause you any trouble.

Answer to your 2nd question:
With an async request, once send() is called, execution continues and handling of the response is dealt with by a callback function. This is slightly more work to code but allows requests to happen "in the background" from a user point of view. Notice that the open method's third parameter is TRUE in this case... (An asynchronous flag)

var objHTTP = 
new XMLHttpRequest(); 
objHTTP.open("POST", strUrl, 
true); 
objHTTP.readyStateChange = 
handleResponse("Request A"); 
objHTTP.send(null); 


alert('Execution continues...'); 

function ResponseHandler(strReq) 
{ 
        if (objHTTP.readyState == 4 ) 
        { 
                alert('Got response:' 
+ strReq); 
        } 
} 

I hope this helps! Please let me know if this doesn't work for you.

—Luke Leonhard, tip author

******************************************

After hearing some feedback, I decided to post a comment to my own tip that offers a robust synchronous cross-browser solution. The Internet Explorer-only solution has simpler functions, so I still use them in a majority of my code. However, when I release external applications, I use the code below.

If you like the tip above, but want it to work on Netscape (6+), Firefox, Safari and Internet Explorer, simply replace the JavaScript functions in this tip with the ones below.

// CrossBrowser Function - - - 
// Builds the XML variables that 
will be sent to the agent. 
function BuildXMLVariables() 
{ 
   // This "If" condition tests the 
browser (for Mozzilla, non IE). 
   //  If you have a specific browser
 test you'd like to do here, thats fine. 
if (document.implementation.
createDocument) 
{ 
        // Mozilla, create a new DOMParser 
        var parser = new DOMParser(); 
        xmlString = "<doc>" 

        // add as many as you'd like
 to here. These variables will be 
accessible via the agent. 
        xmlString = xmlString + 
"<DocID>" + 
document.forms[0].DocID.value +
 "</DocID>" 

        xmlString = xmlString + 
 "</doc>" 

        objDOM = parser.parseFromString
(xmlString, "text/xml"); 
} 



// This "If" condition tests the 
browser (for IE).  If you have a specific 
   // browser test you'd like to do here,
 thats fine.   
else if (window.ActiveXObject) 
{ 
        // IE, create a new XML 
document using ActiveX 
        // and use loadXML as a 
DOM parser. 
        var objDOM = 
new ActiveXObject("Microsoft.XMLDOM") 

        RootEl = objDOM.createNode
(1, "RootElement", "") 
        objDOM.documentElement = 
RootEl 

        // add as many of these nodes
 as you need. 
        // these variables will be
 accessible via the agent. 
        objHeaders = objDOM.
createNode(1, "DocID", "") 
        objHeaders.text = document.
all.DocumentID.value 
        RootEl.appendChild(objHeaders) 
        
        objHeaders = objDOM.
createNode(1, "Status", "") 
        objHeaders.text = document.
all.Status.value 
        RootEl.appendChild
(objHeaders) 
} 

return objDOM; 
} 





// Cross-Browser ----- Calls the Agent 
function runAgent(strAgentName, 
strXMLvariables) 
{ 
   strUrl = "/" + document.forms[0].
pathName.value 
+ "/"+strAgentName+"?OpenAgent"; 

   // This "If" condition tests the 
browser (for Mozzilla, non IE). 
   //  If you have a specific browser 
test you'd like to do here, thats fine. 
if (document.implementation.
createDocument) 
{ 
        objHTTP = 
new XMLHttpRequest(); 
        objHTTP.open("POST", 
strUrl, false); 
   } 
   else if (window.ActiveXObject) 
   { 
        objHTTP = new ActiveXObject
("Microsoft.XMLHTTP"); 
        objHTTP.open("POST", 
strUrl, false, "", ""); 
   } 

   objHTTP.setRequestHeader
("Content-type", "application/x-www-form-
urlencoded"); 

   objHTTP.send(strXMLvariables); 

   resp = objHTTP.responseText; 

   objHTTP = null; 

   return resp; 
}

—Luke Leonhard, tip author

******************************************

Thanks for the Great Tip! I used this approach to trigger agents present on different remote servers, to get calendar information of various users whose mail files are scattered across different servers.

It works great as long as the application on which the agent is present is also hosted on that remote server. But in a scenario where I am not able to host my application on the remote server it fails, and it is logical.

Is there a way I can access information present on a remote server using XML without hosting my application on that remote server? I am using R5 which makes things difficult.

—Syed A.

******************************************

In R5, you are somewhat out of luck (we've had the same problem here). But, your specific question can be answered through a different technique.

I would use the ?ReadViewEntries command via iNotes to retrieve calendar information. Here is the Internet Explorer solution (notice that you could make this cross-browser compatible, using the feedback in this section).

        strXMLvariables = "" 
        objHTTP = new ActiveXObject
("Microsoft.XMLHTTP"); 
        strUrl =
 "http://NOTESPATH/leonha.nsf/($Calendar)/0/?
ReadViewEntries&ExpandView" 
        objHTTP.open("POST", strUrl,
 false, "", ""); 
        objHTTP.setRequestHeader
("Content-type", "application/x-www-form-
urlencoded"); 
        objHTTP.send(strXMLvariables); 
        resp = objHTTP.responseText; 
        objHTTP = null; 
        return resp; 

This will give you an XML result that is your calendar information.

Then you can send the response to an agent via JavaScript (from original tip).

objHTTP = new ActiveXObject
("Microsoft.XMLHTTP"); 
strUrl = "/" + document.all.pathName.value 
+ "/"+strAgentName+"?OpenAgent"; 
objHTTP.open("POST", strUrl, false, "", ""); 
objHTTP.setRequestHeader("Content-type",
 "application/x-www-form-urlencoded"); 
objHTTP.send(objDOM); 
resp = objHTTP.responseText; 
objHTTP = null; 
return resp; 

You can then parse that XML you just sent to the agent. Use the parseFromString function.

 var objDOM = new ActiveXObject
("Microsoft.XMLDOM") 
objDOM.parseFromString(resp); 

Summary

  1. Use JavaScript function to call URL with ?ReadViewEntries from the user's calendar.

  2. Send the XML response from that call to an agent.

  3. Parse the XML in the agent.

  4. Deal with the response to the agent using JavaScript.

Things to remember

  1. The user must have iNotes.

  2. SSO must be enabled among the various iNotes servers and your Web application servers.

If this is unclear, please let me know.

—Luke Leonhard, tip author

******************************************

After receiving some feedback last year, I've used this tip successfully, but with a large caveat -- the way you call the agent depends very much on the version of I.E. that you are using.

Specifically, the line:

objHTTP.open("POST", strUrl, false, "", "");

absolutely must be written as:

objHTTP.open("POST", strUrl, false, null, null);

if it's going to work in I.E. 5.5 and not present the user with a logon prompt each and every time.

The original code works just fine in I.E. 5.0 (tested under Windows NT) and in I.E. 6.0 (tested under WindowsXP) but in I.E. 5.5 (95% of the time) you really need to replace the quotes for the user and password parameters with the word "null" and not use double quotes. Just why this is, and why it fails only 95% of the time using quotes in I.E. 5.5, beats me but it's taken me about three days' work to figure this out. I'm hoping this comment on your great tip will prevent other users having the strange browser behavior I was getting.

Luke, thanks to your previous help, now I can use this anywhere in my Web site. It works just great for locking and unlocking documents on the web.

—Ralph B.

******************************************

Thanks for the feedback. I also use the tip for Document Locking on the web, as well as for forcing individual field updates before closing a document. Also, I developed a set of categorized views that don't cause page refreshes using the same technique.

We use IE 5.5 here in approximately 20% of our users, and have not experienced the double quote parameters causing any errors. However, null seems to work fine in all browsers.

Did you notice the other feedback I provided that made the tip work with Firefox, etc? This, combined with the null attributes, will probably help you out.

—Luke Leonhard, tip author

******************************************

If you have already set the context document as you describe:

Set nsThisSession = New NotesSession 
Set ndbThisDatabase = nsThisSession.CurrentDatabase 
Set ndocThisDocument = nsThisSession.DocumentContext

You can access to source content as per:

objDoc.loadXML 
ndocThisDocument.Request_Content(0) 
txtDocumentID = objDoc.
getElementsByTagName("DocID")(0).text 
txtStatus = 
objDoc.getElementsByTagName
("Status")(0).text

Why do you use the objDoc.LoadXML command here? If the context document is truly available to the agent, you could say something like:

Var = ndocThisDocument.
Request_Content(0) 
Similar to the txtDocumentID etc 
references below it

I must note that I have had little joy with direct context document based approaches with Ajax related agents.

—Owen B.

******************************************

The document context of the JavaScript call contains the XML that we passed to the agent. In the example, it would look something like:

<doc> 
<DocId>0987543298475ad234<DocId> 
<Status>TestStatus</Status> 
</doc>

Because the content we posted with the agent is XML, I use LoadXML on the server side to easily get at the data in that XML. This is the most reliable way I have been able to utilize Ajax-style agents thus far.

I'm also somewhat confused by your comment "You could say something like: Var = ndocThisDocument.Request_Content(0) Similar to the txtDocumentID etc references below it." The txtDocumentID references are in fact different from the RequestContent(0) line because the txtDocumentID variable is being pulled from the XML Tag. Notice this line: objDoc.getElementsByTagName("DocID")(0).text

I hope this information helps. This is the most reliable way I've discovered that allows Ajax-related calls to work inside Domino. Good luck!

—Luke Leonhard, tip author

Do you have comments on this tip? Let us know.

This tip was submitted to the SearchDomino.com tip exchange by member Luke Leonhard. Please let others know how useful it is via the rating scale below. Do you have a useful Notes/Domino tip or code to share? Submit it to our monthly tip contest and you could win a prize and a spot in our Hall of Fame.

This was first published in October 2004

Dig deeper on JavaScript for Lotus Notes Domino

0 comments

Oldest 

Forgot Password?

No problem! Submit your e-mail address below. We'll send you an email containing your password.

Your password has been sent to:

SearchWinIT

Search400

  • iSeries tutorials

    Search400.com's tutorials provide in-depth information on the iSeries. Our iSeries tutorials address areas you need to know about...

  • V6R1 upgrade planning checklist

    When upgrading to V6R1, make sure your software will be supported, your programs will function and the correct PTFs have been ...

  • Connecting multiple iSeries systems through DDM

    Working with databases over multiple iSeries systems can be simple when remotely connecting logical partitions with distributed ...

SearchEnterpriseLinux

SearchVirtualDataCentre.co.UK

Close