bookmark_borderQUINDIP: intercept wrong/inexistent documentId

Hi

long time no post… but alas.. finally something I really like:

Have you ever had the situation, that you have a xPage which is workflow enabled, and you have (maybe) multiple servers? Some users might even use the LN client front-end on another server. The workflow sends out a mail telling the approver to approve.

Boom… the document did not yet replicate to the target server…

you do not want either of this message displayed:

what to do?

hmm.. I first tried with this, sorry, was not happy with it…

thing is: we use the same xpView for production and for archive, only the data source changes in the background. This will open the documents from the archive with such an URL:
http://server/db.nsf/%24%24OpenDominoDocument.xsp?databaseName=server!!otherDb.nsf&documentId=6496BA354B37544CC1257C680059C04B&action=openDocument

I could not get this to work..

So I had to figure out another way to intercept a “wrong” documentId.

Solution: use a phaselistener!!! (how to implement see here, kudos to Sven)

Now I only need to intercept the first phase in the JSF lifecycle, test if the supplied documentId exists and redirect:

event.getFacesContext().getExternalContext().redirect(“xpDocumentNotFound.xsp”);
event.getFacesContext().responseComplete();

I do some checks and then call redirect and responseComplete which executes immediately and does not continue with the other JSF phases

and the best thing: this is UNIVERSAL!!! I don’t need to implement it for all xPages I use!

bookmark_borderQUINDIP: need to run an agent as signer from an xPage?

we needed a possibility for users to “override” the workflow, ie. interfere even though they were not “authors” at the moment. The only way was to do with an agent running with more access rights.

solution was: us “sessionAsSigner”

this would not work:
var agent = currentDatabase.getAgent(“(agnXPageWFMoveToState60)”);
agent.runWithDocumentContext(currentDocument.getDocument() );
return “xsp-reopen”;

this did work (kudos to Sven Hasselbach):
var agent = sessionAsSigner.getCurrentDatabase().getAgent(“(agnXPageWFMoveToState60)”);
agent.runWithDocumentContext(currentDocument.getDocument() );
return “xsp-reopen”;

hope this helps some people to achieve similar things

bookmark_borderINDIP: add visual indicator to type ahead

that’s how it looks:

field with type ahead waiting for input:
field with type ahead thinking
field with type ahead failure

this is the XML code of the field for the events:
<xp:eventHandler event=”onkeypress” submit=”false” id=”eventHandler2″>
<xp:this.script><![CDATA[if (event.keyCode==9 || event.keyCode==13){
removeVisual(‘view:_id1:inputText3’)
}else{
addVisual(‘view:_id1:inputText3’);
}]]></xp:this.script>
</xp:eventHandler>

<xp:eventHandler event=”onblur” submit=”false” id=”eventHandler3″>
<xp:this.script><![CDATA[removeVisual(‘view:_id1:inputText3’)]]></xp:this.script>
</xp:eventHandler>

and this is the style assigned to the field:
<xp:this.styleClass>
<![CDATA[#{javascript:if( currentDocument.isEditable()){ return ‘useTypeAhead’; }}]]>
</xp:this.styleClass>

this is needed in the CSS:
.useTypeAhead{
background: #fff url(“TypeAhead.gif”) no-repeat right;
}
.dijitValidationContainer{
width: 20px;
}
.dijitValidationContainer .dijitValidationIcon{
width: 20px;

this is needed in a CSJS library (cudos to Mark Roden and Sven Hasselbach)
function x$(idTag, param, jd){ //Updated 28 Feb 2012
// hardcoded
idTag=idTag.replace(/:/gi, “\:”)+(param ? param : “”);
return( jd==”d” ? “#”+idTag : $(“#”+idTag));
}

var inputField;

//addVisual function used to add a loading image to the screen and make it visible
//expected input string similar to view1_:id1_:viewPanel1
function addVisual(idTag){
var newImage=”url(‘loading.gif’)” //Change me for your server
inputField=dojo.query(x$(“widget_”+idTag, ” .dijitValidationIcon”, “d”))
inputField.style(“backgroundImage”, newImage);
inputField.style(“visibility”, “visible”);
inputField.style(“backgroundRepeat”, “no-repeat”);
inputField.style(“backgroundPosition”, “right”);
dojo.query(x$(“widget_”+idTag, ” .dijitValidationContainer”, “d”)).style(“display”, “block”)
inputField.attr(“value”,””)

}


//— hijack dojo XHR calls
//Thanks to Sven Hasselbach for this ajax intercept code
//
var typeAheadLoad;

dojo.addOnLoad( function(){

 /*** hijacking xhr request ***/
 if( !dojo._xhr )
    dojo._xhr = dojo.xhr;

 dojo.xhr = function(){
    try{
       var args = arguments[1];
       if( args[‘content’] ){
          var content = args[‘content’];
             if( content[‘$$ajaxmode’] ){
                if( content[‘$$ajaxmode’] == “typeahead” ){
               
                   /*** hook in load function ***/
                   typeAheadLoad = args[“load”];

                   /*** overwrite error function ***/
                //   args[“error”] = function(){
                 //     alert(‘Error raised!’)
                //   };
                   
                   /*** hijack load function ***/
                   args[“load”] = function(arguments){
                      /*** On Start ***/
                     // alert(“On Start!”);
                   
                      /*** call original function ***/
                      typeAheadLoad(arguments);
                   
                      /*** On Complete ***/
                  if (arguments.toLowerCase()==”<ul></ul>”){
                  var newImage=”url(‘typeAheadFailure.gif’)” //Change me for your server   
                  inputField.style(“backgroundImage”, newImage)   
                  }else{
                  inputField.style(“background”, “white”)
                  }
                   };
               }
           }
       }
    }catch(e){}
    dojo._xhr( arguments[0], arguments[1], arguments[2] );
 }
});


//removeVisual function used to remove the loading image from the screen and make it hidden
//expected input string similar to view1_:id1_:viewPanel1
function removeVisual(idTag){
var inputField=dojo.query(x$(“widget_”+idTag, ” .dijitValidationIcon”, “d”))
inputField.style(“backgroundImage”, “”)
inputField.style(“visibility”, “hidden”)
dojo.query(x$(“widget_”+idTag, ” .dijitValidationContainer”, “d”)).style(“display”, “none”)
}

//Checks to see if the visual icon is still running – if so then assume fail and kill it
function checkTimer(idTag){

dojo.query(x$(“widget_”+idTag, ” .dijitValidationContainer”, “d”)).style(“display”, “none”)

}

bookmark_borderINDIP: how to figure out the id of an element which triggered an event!

here’s the scenario:

We have what we call dynamic tables. It lets the user add as many rows as he requires. Really neat.
In some of the fields we use type ahead.

Recently I figured: it would be nice to see an indicator if the server is “thinking”… something like
I could do it easily via one specific field from which I knew it’s id …

But in those dynamic tables I have no clue on which field the event for type ahead was triggered….

until now:
Cudos to Paul Calhoun with his incredibly useful tips about CSJS (something I usually avoid as much as I can)

There I found the solution! You can call SERVER side JS from CLIENT side JS… isn’t that fantastic?

here’s how:
call the SSJS like this:
“#{javascript:<ServerSide JavaScript>}”

example to alert the id of an element via it’s event, in this case onFocus, which was triggered:
alert( “#{javascript:BackingBean.getElementId(this)}” )


For those ones interested what the getElementId does in my Java Bean:
UIInput input = (UIInput) handler.getParent();
return input.getClientId(FacesContext.getCurrentInstance());

Of course the UIInput could be changed to something more generic like UIComponent

stay tuned for my next blog which tells how I achieved proper visual indication

bookmark_borderINDIP: How do you export data from an web application into Excel?

You could use POI to write an Excel file, but writing to a file with APIs is usually really slow.

MYYYYYYYYYYYYYYYYYY approach is blazingly faaaaaaaaaaaaaaaaaaaaast……..

In our company we are in the lucky situation that we have single-sign-on on the web towards our Domino servers. This is important for the following solution to export documents to work properly.

Steps:
1. Create some kind of “profile” document which defines the filters for the data the user wants to export
2. Create an agent which processes those filters from the profile document and outputs XML for all documents
Example XML:
<?xml version=”1.0″ encoding=”UTF-8″?>
<data xmlns:xs=”http://www.w3.org/2001/XMLSchema-instance”>

<document>
<processeddate xs:nil=”true”></processeddate>
<wfcurrentstate><![CDATA[70]]></wfcurrentstate>
<wfcurrentstatename><![CDATA[Returned for modification]]></wfcurrentstatename>
<timecreated>2013-10-08</timecreated>
<regionname><![CDATA[SOUTH AMERICA]]></regionname>
</document>
</data>

Important here is only the “xs:nil” attribute for “empty” fields, dates/numbers you can submit as is, text fields I advise to put into the <![CDATA[textvalue]]> format.
3. Import the XML via the URL of the web agent (eg. http://yourserver/yourpath/youragent?openagent) file into Excel via Data Import from other sources from XML data import.
Now the first time you do this you are have to go through some wizard actions done by Excel.
Once the XML map  has been created you can use that file over and over again (say as template). You simply need to refresh the data via the data tab in Excel.
My development workflow (to get this working start slow)
first only export some text fields
then try also empty fields (with the xs:nil) attribute
then continue with numbers and dates to see if it works
call the agent directly in the browser to check if the XML is properly formatted (Google Chrome will tell you so, maybe other browers as well)
then import that XML into Excel, ie. use the refresh button
I use to “log” the filters from the profile when the agent runs. My agent uses mostly FT Search to find the documents, only if more than 5000 are found it reverts to (if possible) view categories.
The “constructed” query is also logged. That way I can run the same query directly in the LN client to check for FT Query mistakes. As example I figured I need to put texts into double quotation marks to make queries, with filters consisting of more than one word, properly working.
The result is phantastic! I can export 5000 documents via VPN connection from my home office in about less than 30sec!
Questions? please don’t hesitate to write comments
otherwise enjoy!!!

bookmark_borderQUIND: Profile documents: sort of ;-)

I’m sure you also need sort of “profile documents” once in a while. Documents where a user can store some values he wants to use over and over again, in Web technology term thing of a “cookie”.

This is plain easy!

Simply open the xPage and as a Data source document id (red box) define a computed value like this:

Document ID code:
var doc:NotesDocument = database.getView(“lupUserProfiles”).getDocumentByKey(session.getEffectiveUserName(), true);
if( null != doc ){
return doc.getUniversalID();
}

that way the xPage will either be displayed empty (when no such “profile” document exists) or it will use the appropriate user profile it finds.

In the QuerySave (or somewhere else) you need to make sure to save the user name and have it in the view as sorted first column of course

QuerySave code:
try{
var doc:NotesDocument = docCurrent.getDocument();
var item:NotesItem = doc.replaceItemValue(“userName”, session.getEffectiveUserName())
item.setAuthors(true);
}catch(e){
// handle error
}

bookmark_borderError 500: your thoughts?

Developing xPages i’m sure you also have come across those nasty “Error 500” errors.

This bugs me right now. The error.log I get won’t help me at all.

Any ideas how to easier figure out the problem?

In my case the error.log simply states: “Unable to find component with ID xyz”.

Interestingly: the component IS visible.

I am a bit lost here: this happens in my xPage only when I copy the contents (some of them) to a new document which is then redirected to and opened in edit mode.
If I create the new document afresh all works as it should.
If I “copy all Items” to the new document then it also works fine.

any ideas?

bookmark_borderBUG in Lotus Notes applications: saving documents with private folders (not stored on desktop) and folder references enabled

Long time no see, but stay tuned, there will be more updates again soon regarding the xPages development from my side.

This week we have a very nasty bug. When people tried to save documents they got an “You are not authorised to perform that operation” error message.

After a while fiddling around we figured: We copied&pasted documents we could save the newly copied documents. So where was the difference? We used TeamStudio Delta to figure out differences between the two documents. It turned out mostly just the “Folder References” fields.

I googled and surely enough found this bug report: http://www-01.ibm.com/support/docview.wss?rs=463&uid=swg21099783

It basically states that users do get this error when folder references are enabled and the documents are stored in private folders which are not stored on the desktop.

maybe this information helps anyone out there to not “desperate” if faced with a similar issue.

yours

Michael

bookmark_borderHAPPY!!! first full featured xPage application productive!

I am happy to report that our first full featured (integrated workflow, cool typeAheads, fantastic validation, dynamic tables and many more features) is fully productive!

Thanks to all readers and commenters on my blog and all other bloggers out-there giving me help and insight into the deeper workings of the xPages.

Summarized main difficulties:

  • getting the validation to work the way we required (ie. without needing ExtensionLibrary tabbed tables, all fields should be validated, even if currently invisible and the validation should be customizable by the customer himself)
    • Solution was: not using validators at all, simply prevent the save with a condition on the action group in the save buttons, the condition is a “result” from our custom validator which sets JSF messages where appropriate
  • customized typeAheads (see this blog)
  • workflow integration (we can continue to use the LN client based code, which is important as the interface is now working on both “clients”, notes and web)
  • certain parts of the form stored in different documents (see this blog)
  • dynamic tables which lets the user add new rows to tables of data
  • simple yet full featured and customizable views, using the DynamicViewPanel with different filter options per selected view, customizable (via styles) view columns
  • fancy field help (see this blog)
Result:
What we now have is a full featured framework which will help us create future xPage applications in a fraction of the time spent on this one! As a matter of fact we could create a simple form/view db with full featured workflow within one day!!!!!!!!!!!!! Most of the work actually could even be “outsourced” to the customer, work such as “defining” field help, column customizations, validations etc.
Best features in xPages (imho):
  • CustomControls!!! change in one place an all occurrences are updated! Subforms in the old LN days were no match at all!!!
  • typeAheads
Worst feature in xPages (imho):
  • error messages from server: an HTML 500 error is of no use, sometimes I needed to dissect the whole app again to find where the problem was
  • OneUI: it’s a pain in the ass, but we did it, to figure out how to properly change certain GUI elements look and feel (such as typeAheads)
Recommendation:
  • don’t use JavaScript (or only to call backend Java code) !!!! debugging JavaScript is a pain in the ass where as debugging Java is so straight forward (see this blog) and the speed of the code execution is SOOOOOOOOOOOOOOOOOOOOO freaking fast!!!
  • use where ever possible “static” methods so you don’t need to instantiate the class, eg. our StandardUtil class has ONLY static methods such as: getKeywordValues, searchUser (for typeAhead), getSortedDocumentCollection, getEmptyDocumentCollection, hasAccess, hasRole etc.
I now need some rest. The past few weeks were mostly sleepless. Any recommendation for a nice “get-well-again” resort close to Switzerland are welcome 😉 ! (can be south Germany or Austria as well)