Quick Domino Tip: Dual-Purpose Agents
When developing I always try to use as few design elements as possible. It's a thing of mine, although surely not uncommon?
Last week I needed to do some server-side validation. First I wanted to validate in "real time" using Ajax to give the user feedback as they filled out the form and then, second, I wanted to validate again when the form was submitted (you should always validate on save).
I did something I'd never done before and which saved adding two new elements to do the same job; I used an Agent that works out whether it's being called directly via a URL call or whether it's being ran as part of the form's WQS event.
Here's How
It works in Java or LotusScript, but here's the concept as seen in Java:
import java.io.PrintWriter; import lotus.domino.*; public class JavaAgent extends AgentBase { public void NotesMain() { try { Session session = getSession(); AgentContext agentContext = session.getAgentContext(); Document docContext = agentContext.getDocumentContext(); PrintWriter out = getAgentOutput(); //Run code common to both agent calls here. if (docContext.getItemValueString("Query_String").toLowerCase().indexOf("openagent")==0){ //Run code specific to Ajax calls (and return some JSON) out.println("content-type: text/plain"); out.println("{\"valid\":true}"); } else { //Run code only needed for WQS docContext.replaceItemValue("Valid", "1"); } } catch(Exception e) { e.printStackTrace(); } } }
As you can see it works out if it's an Ajax call or not based on the Query String. Yes, even agents ran directly via the URL have a document context!
As it's a WQS agent it needs to be set to run on no documents and from the agent list. You can have your Ajax call the agent directly using the bracketed notation like this:
http://www.codestore.net/user.nsf/(ValidateCellNumber)?OpenAgent
You can also run the same agent in the Form's WQS:
One less Agent to maintain!
At some point I'll include this trick in a working Cell Phone Validator demo I plan on building in to Dext.
Gotcha
It's not perfect, as it relies on the agent being called with it's full formal URL of ?OpenAgent and not just a simple ?Open. But hey, that's a trade-off I'm willing to make.
Why can't you also test for "?Open"? Is there a case when you'll run into that where you're not directly calling the agent?
When I would call for a form or document, I try to do it in such a way that a query string command isn't even needed.
Reply
There's a chance that the Query_String for the agent when acting as a WQS could be ?Open&seq=1 if the form was also opened using just the ?Open URL command.
Reply
If you're using jQuery for the AJAX requests you can also look at the "HTTP_X_REQUESTED_WITH" header. It'll contain "XMLHttpRequest" for AJAX requests. So there won't be a need for URL parameters. I don't have experience with other JS frameworks, so YMMV.
Reply
Good point. I hadn't thought of that. My Ajax was Dojo-driven, which looks like you can specify custom headers like so:
dojo.xhrGet({
url: 'agent?openagent',
headers: {'X-Ajax': 'yes'}
});
But. How do you get a handle on the headers from the Java (or lotusscript for that matter) code?
Reply
Hi Jake,
you can use NotesSession.documentContext. It gives you access to the request document. The request document contains Notes items for everything in the header. As Thimo pointed out there is an item HTTP_X_REQUESTED_WITH and in your example there would be a HTTP_X_AJAX Notes item (and a HTTP_BLAH item when you add a 'bla' : 'blub' to your headers in the request.
In a nutshell: HTTP headers turn into notes items with a HTTP_ prefix when you do ?OpenAgent
Reply
Interesting. Never knew that. Thanks Stephan!!
Reply
Hide the rest of this thread
I wasn't sure either, so I whipped up my favorite web agent:
Option Public
Option Declare
Sub Initialize
Dim s As New NotesSession
Dim doc As NotesDocument
Dim item As NotesItem
Set doc = s.Documentcontext
Print |<style type="text/css">|
Print |table {width : 100%; border : 0}|
Print |.itemname {background-color : #FFFFCC}|
Print |.itemvalue {background-color : #CCFFFF}|
Print |</style>|
Print |<table>|
Forall curIt In doc.Items
Set item = curIt
Print |<tr>|
Print |<td class="itemname">|
Print item.Name
Print |</td><td class="itemvalue">|
Print item.Text
Print |</td></tr>|
End ForAll
Print |</table>|
End Sub
:-)
Reply
forgot to point out: 'X-Ajax' turns into 'HTTP_X_AJAX'. So it is capitalized and the dash turns into an underscore.
Reply