Lapas

Friday, 2 November 2012

Lotus Notes. Getting user HOME on MACINTOSH

I had run in problem building application for MACINTOSH in LN. It was required to install some user files for regular use and it should be installed in user home directory. So then problem was, that Lotusscript does not gives user home directory location using Environ, or NotesSession.getEnvironmentString.
First my attempt were to execute shell like "env > /tmp/userenv.txt" and then read the file, parse and get HOME directory from file - I don't like this, because You need to create a file on disk.
Second attempt were to search for dll containing standard C function getenv, which must return environment variable by specified name. So the library found is "libSystem.dylib" and resulting declaration in LS library, agent {Declare Function mac_getenv  Lib "libSystem.dylib" Alias "getenv" (ByVal varnameAs String) As String}, so call mac_getenv("HOME") and voila!


Creating Type-ahead searches in Lotus Notes

This will be short article about how to build user friendly type-ahead searches for Lotus Notes clients.
This will not be tutorial, but only idea and some basic concepts.
Basic instruments:
1) Form - dialog basic form
2) Dialog list field - search text entering
3) Query recalc - search performing
4) NotesTimer - for large database search optimization
5) Table with hotspots - user friendly result display

Example to-do:

  1. Required to build dialog for searching data in database (people, organization, product or other object search).
  2. Build a dialog form, which will be called from our hotspot in some basic form (better to use dialog, because of complex design of search dialog)
  3. Add dialog list field, and put it in table, disable delimiters, add formula "" for choices, check refresh on each character.
  4. On query recalc add function to perform search before form is refreshed, get value from search field and compute formula and search for results. After search put result in fields, from whose will be computed out table. Maybe You will need to limit search starting from 2-3 characters entered in searchfield.
  5. Create table with row count about 10 result rows. You could build paged table or table with hotspot for more results in ex. ws.prompt LIST. Each row could consist of index, name of found entry, id, other info ex. No., Product Name, ID, Manufacturer. For table correctly to work it is required to add bunch of hide formulas and field formulas, for faster results, build one row with field indexes ex. No_1, Name_1, ID_1,Man_1, but results put in fields No,Name,ID,Man, so You can compute which entry to pick from result field for row ex. @getfield(@left(@thisname;"_"))[@tonumber(@right(@thisname;"_")] (add error handling :) ) - it's pretty easy to build row with this technique. For pickfield ex. Product Name add hotspot, to allow to pick clicking on entry directly and add script like call entrySelected(0). For faster 10 row run initially create fields with no index and then use copy/paste, because LN will add indexes - if You copy field Name, then paste result will be Name_1, next Paste, Name_2 (pretty easy).
  6. Next searched database contains a lot of records and each search if about 1,2 seconds, then it could be possible to add optimization with NotesTimer. Put code to create timer on queryrecalc and call search code on alarm, so if user types fast, then reset timer on refresh - do not perform search. If user stopped typing, then in about second Your search code will be executed (limitation of notestimer - no millisecond execution intervals) - search, put results, refresh.
Thank You for attention


Wednesday, 31 October 2012

One or more of the source document's attachment are missing

"One or more of the source document's attachment are missing" appearing for Design refresh or design copy.
Here is the way to try to fixup database without deletion of elements:
1) Create empty database
2) Set inherit design from corrupted database
3) Perform design refresh
4) On error check status bar for last design element displayed
5) Goto corrupted database and resave design element

In my sutiation main problem were with agents. So resaving them solved then problem.

Thursday, 30 June 2011

WEBDAV in XPAGES

What is WebDav, you can read at http://www.webdav.org.

WebDav and Lotus Domino
Basicaly webdav natively is supported only for managing file resources in .nsf databases. Read this for details: http://www.codestore.net/store.nsf/unid/FISR-6U8SN7

Next question is how to create webdav service for working with documents or set's of documents. This is very important when you're building DMS (document management systems). So why so late to create webdav in only 8.5 for document management?
Main problem,that there were no methods to pass back/receive binary data to browser, so webdav could be created only for txt type documents. Now binary data could be passed back using servlet response streams and read using request streams. Check the article about binary response in xpages.

Now our goal is to make available document listing and attachments for reading through webdav from simple database. db url is http://server/folder/db.nsf
Our webdav service will be served at xpage webdav.xsp. So full service url is http://server/folder/db.nsf/webdav.nsf

Now we have to create enable webdav on the server (native webdav support configuration), create website substitutions for better links (read this: http://www.dominoguru.com/pages/domino_rest_xpages_part1.html) for example when we entering url http://server/folder/db.nsf/webdav/documentid/attachmentname we get attachment, http://server/folder/db.nsf/webdav/documentid - attachment list for document. So basic substitution could be /folder/db.nsf/webdav/* -> /folder/db.nsf/webdav.xsp

Now we have create webdav.xsp, which will contain main service code. I will show only simple example, but it will be enough to start thinking in that way and build Your more complex application.

All service code could be put in beforeRenderResponse:
xpage main settings:
1. gzip compression for xpages disabled!
2. create form =false
3. rendered = false

Init code
var req:javax.servlet.http.HttpServletRequest=facesContext.getExternalContext().getRequest();
var resp:javax.servlet.http.HttpServletResponse=facesContext.getExternalContext().getResponse();

@code end we should end with
facesContext.responseComplete(); //because domino will generate html if we don' t do that

Next we should create blocks for handling requests, which contains of if's on request method. Ex. if (req.getMethod()=="OPTIONS"){} and so on
Each operation must return correct status code using resp.setStatus()

Main methods used for basic webdav support are:
1. "OPTIONS" - resource options, not required, should send (status: 200)
2. "PROPFIND" - property listing/dav folder contents with status (207)
3. "GET" - for resource downloading

now the theory of request urls and substitution. when user requests url like http://server/folder/db.nsf/webdav/documentid server will redirect it to http://server/folder/db.nsf/webdav.xsp and original requested url will be available through req.getRequestURI(), which sould be parsed, to determine what to do with request and what kind of response should be formed. For example if we got request with url like: http://server/folder/db.nsf/webdav/documentid - we could return attachment list in document(PROPFIND method), when user browses folder and clicks on document/attachment GET request is initated with HREF specified in returning XML by PROPFIND method handler. So on get request we are returning attachment data.

Look for sample code:
In sample code on propfind returned attachment list in document, which found by unid.
In get request attachment data is returned.


var req:javax.servlet.http.HttpServletRequest=facesContext.getExternalContext().getRequest();
var resp:javax.servlet.http.HttpServletResponse=facesContext.getExternalContext().getResponse();

var result="";
if (req.getMethod()=="OPTIONS"){
resp.setStatus(200);
resp.setHeader("DAV","1, 2")
resp.setHeader("MS-Author-Via", "DAV")
resp.setHeader("Allow","OPTIONS, GET, HEAD, POST, DELETE, TRACE, PROPPATCH, COPY, MOVE, LOCK, UNLOCK, PROPFIND")
resp.setContentType("application/xml")
resp.setContentLength(0);
}else if (req.getMethod()=="PROPFIND")// req.getMethod()=="GET")
{
resp.setStatus(207);
resp.setHeader("DAV","1, 2")
resp.setHeader("MS-Author-Via", "DAV")
resp.setHeader("Allow","OPTIONS, GET, HEAD, POST, DELETE, TRACE, PROPPATCH, COPY, MOVE, LOCK, UNLOCK, PROPFIND")
resp.setContentType("application/xml; charset=utf-8")

var writer=resp.getWriter();

result+=""
result+=""
result+=" "+req.getRequestURI()+"/folder/ folder HTTP/1.1 200 OK "
try{

var doc:NotesDocument=database.getDocumentByUNID("DBFDB0C60BF0B073C22578BE00406E45")

attachs=session.evaluate("@AttachmentNames",doc)
for (i=0;i "+req.getRequestURI()+"/"+attachs[i]+""+attachs[i]+"application/octet-stream"+emb.getFileSize()+" HTTP/1.1 200 OK "
}
}catch(e){

}

result+="
"

writer.write(result);
writer.flush();
resp.setContentLength(result.length);
facesContext.responseComplete()

}else if (req.getMethod()=="GET"){
resp.setStatus(200);
var writer=resp.getOutputStream()
resp.setHeader("DAV","1, 2")
resp.setHeader("MS-Author-Via", "DAV")
resp.setHeader("Allow","OPTIONS, GET, HEAD, POST, DELETE, TRACE, PROPPATCH, COPY, MOVE, LOCK, UNLOCK, PROPFIND")
resp.setContentType("application/msword")
resp.setHeader("Content-Disposition","attachment; filename=testdocument")

var doc:NotesDocument=database.getDocumentByUNID("DBFDB0C60BF0B073C22578BE00406E45")
var attach:NotesEmbeddedObject=doc.getAttachment("testdocument")
if (attach!=null)
{
var fin:java.io.InputStream = attach.getInputStream()
var flength=fin.available();
while (fin.available()>0)
{

writer.write(fin.read());
}
writer.flush();
}
resp.setContentLength(flength);
facesContext.responseComplete()
doc.recycle()

}



P.S. if You need some detailed info on this or some help, then just PM, i' ll write back and post additional comment on this. Sorry, but have no time to fully explain (described just main steps)

Monday, 27 September 2010

Using XPAGES to pass back binary data

So I would like to write a little article about passing custom generated responses using XPAGES and how to use the for binary output.

Main problem in Lotus Domino editions till 8.5 is that we could not pass back binary data directly to WEB browser (using agent print/printing in form and so on). The only ways were, to create document, attach file, redirect attachment link or create file in shared web dir and pass direct link to user.

Now, when XPAGES with JSF technology comes to place it is possible to do so. Let's get it "how?".

At first You need to create and xpage element and set it render property to false - so nothing to be rendered.
Then You should choose xpage event, where to write the right code, which will serve data. So xpage has following events "beforePageLoad","afterPageLoad","beforeRenderResponse","afterRenderResponse","afterRestoreView". Most interesting for our example are "beforeRenderResponse","afterRenderResponse".

Classes to work with:
1. javax.faces.context.ExternalContext - needed to get response object
2. com.ibm.xsp.webapp.XspHttpServletResponse - needed to get outputstream
3. javax.servlet.ServletOutputStream - stream, where we will write data

XPAGE options to be set:
CreateForm = false
rendered = false

//CODE BLOCK START FOR this example it should be put in beforeRenderResponse
//Initialization
var extContext:javax.faces.context.ExternalContext = facesContext.getexgetExternalContext();
var response:com.ibm.xsp.webapp.XspHttpServletResponse = exCon.getResponse();
var writer:javax.servlet.ServletOutputStream = response.getOutputStream();

//setting response headers for browser to recognize data
response.setContentType("image/gif"); // content type of data, for other binary see mime types or put "application/octet-stream"
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Content-Disposition" ,"attachment; filename=image.gif"); //required if You need to pass it back as file

//convert sample hex to character codes.
var decoded=imgHex.split(" ");
var result=new Array();
for (var i=0;i<decoded.length;i++)
{
result[i]=(parseInt(decoded[i],16))
}
// result is an array containing character codes (byte array). How to prepare for other situations an array You should decide at Your own.
writer.write(result); // just write prepared array to output

//when all output is done. just tell that You have completed the response and close stream
facesContext.responseComplete();
writer.close();




//Sample data
//domino image in hex should move to initialization section
imgHex= "47 49 46 38 39 61 15 00 15 00 e6 00 00 00 00 00 ff ff ff ce 64 66 c9 65 67 e2 7b 7c ff 98 99 ce 65 69 a4 9f a3 68 66 69 a0 b5 ca c8 cc cd c9 cb ca 65 65 63 67 67 65 e5 e5 e2 ca ca c8 c7 c7 c5 f0 f0 ef eb eb ea a8 a8 a7 fe f3 8a fe f2 8c ff f2 8c fe f1 8b d6 d4 d0 ec eb e9 ea e9 e7 c8 c7 c5 f3 ac 38 f2 ad 38 f2 ac 3a f2 ac 3d fc be 59 fc be 5b f9 be 5a d0 cd c8 f4 ab 3a fc bc 5b fc bd 5c fb bc 5d e8 e6 e3 de dc d9 d7 d4 d0 d0 cd c9 d1 cd c8 c0 be bc ee ed ec 81 5b 48 84 59 46 82 5b 4a b7 83 6d b6 82 6d 83 59 49 84 5a 4a b8 81 6d 87 59 4a 84 5a 4c b8 7f 6c ba 7f 6d cd 65 64 cd 67 65 ca 66 66 ff 9a 98 ff 9b 99 fc fc fc ca ca ca ad ad ad ac ac ac 88 88 88 67 67 67 66 66 66 ff ff ff 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 21 f9 04 01 00 00 47 00 2c 00 00 00 00 15 00 15 00 00 07 9e 80 47 82 83 84 85 86 87 88 89 8a 8b 8c 06 03 8c 86 3d 3e 3f 3c 86 42 97 98 99 39 45 05 04 3b 0a 85 42 40 a3 a4 a3 36 14 43 46 02 0f 96 40 11 af b0 3a 14 20 24 30 46 0b ad b0 af 32 14 27 1d 31 07 46 41 ad 19 13 44 12 33 17 25 1c 37 13 18 c2 ad 0e 10 1b 3a 16 26 1f 38 13 0b 40 d0 a1 40 2e 13 33 15 22 1c 2f da 29 dc c3 de 1a 2d 08 21 1e 34 13 2a 18 18 e9 c4 28 0d 0c 35 13 09 2c 2b 23 f6 bc 95 1a 38 aa 1b 21 23 08 13 2a 54 a8 ae 50 90 87 10 23 46 84 44 b1 a2 c5 8b 8c 02 01 00 3b"

REFERENCES, where You could check how to output STRING data.
http://www.wissel.net/blog/d6plinks/SHWL-7MGFBN
http://www-10.lotus.com/ldd/nd85forum.nsf/7756aedc25e6d81285256324005ac76c/1e87cc63dfcf9412852575ae0005079f?OpenDocument - getResponseStream problem.

Saturday, 10 April 2010

Embedded view from External DB

1. Standard method replacing replicaid in form using dxl.
2. Advanced method using forms, frameset and embedded editor.

You have 2 databases, form1 (db1) and view1 (db2) which should be displayed. How to make it without composite applications and DXL tools?

Simply:
1. Create frameset1 in db1 with 2 frames, Top and Bottom. Make top frame 1 Pixel height.
2. Create empty form in db1, let's call it Form1Embed, set it to open in frameset1 Top frame.
3. Create form or page in db2, let's call it Form2Embed. Add embedded view1 to this form. Then add simple code in postopen:
dim ws as new notesuiworkspace
dim uidoc as notesuidocument
set uidoc=ws.currentdocument
'// fill some field with universal id of uidoc.document, then set single category from this field for view1
What this code do? the uidoc we get is parent document (form1).

3. Embed Form1Embed in form1 through embedded editor.
4. In frameset1 Bottom frame compute db2 path and our created element Form2Embed (that Form2Embed could be opened)

Now what happeps when you open form1?
1. form1 is loaded
2. Embedded editor loaded for form1embed, lotus opens this form in frameset1 top frame, which is not visible. Then computes Bottom frame with our form2embed from db2, which on load gets form1 document and uses it unid as single category key.


Lotusscript External DB calls

Sometimes, it is required to call functions from other related databases and then we have to choose copy libraries or try to call using parameters, so that libraries stay where they belongs to.
At least we have 3 possible call methods to access script from other LN database.

1. Calling agent with parameterDocID, where we could pass 4 bytes of data (note id length FFFFFFFF). Traditionally noteid of profile document or other document to process is passed. For more flexibility, can be used bit masks if agent works with UI and has access to context.
Main problem for this method is restrictions to passing parameters and for agents contains large libraries each time agent is loaded, script libraries are loaded again and again and no cached.

2. Second method is calling dialogbox in external database, by creating document in that db. Then we can pass parameters with no document save, use lotus cache (form cached) and access external lotusscript. Main problem is to reopen document after processing if needed, so that code should be splitted in 2 dbs, call function will handle return data and that we normally could not hide that dialog if we need if we want to return data.

3. Third method is the trick (some type of mutation of second method) found is that each document or UI element where external functions could be called is enclosed in frameset with 1 pixel top frame. Then form should be setup to open in frameset second frame. Visually document opens as always and no UI difference visible. Top frame is used to call external function. How to call?
3.1 simple method. In external database create form, create postopen event on event start you can access context uidoc using notesuiworkspace.currentdocument. No params could be passed in this case. But this method uses cash, which creatly boosts performance. To call form use notesuiworkspace.composedocument in top frame.
3.2 advanced method. use the same technique as in 3.1, but no post open event, only cache source document, for later use. Then create on form at least 2 editable fields: 1 for initial focus, 2 for function call. in second field write code in entering event. How to pass parameters? in caller db script. like in 3.1, but now we need returning uidoc to pass parameters. Use like set uidoc=ws.composedocument. Now use uidoc.document to write parameters in opened doc, after finished, just use uidoc.GotoField(second field). This method is like calling extenal agents with more flexibility and cache.