Lapas

Monday, 15 December 2014

Copy/Paste from applications to Notes into text fields (international characters)

The problem (sample with cyrilic symbols):


I had a very interesting problem with copying data, which contained international characters, from Microsoft office applications to Lotus Notes. So if you copy/paste into RichText field, there is workaround, that it could be pasted as plain text, but when you try to paste into Notes style text field, there is no options to do that. Using Lotus Notes e-mail, I've noticed, that copy/pasting into Native OS Style fields works well. So the workaround for that type of problem is to create form with one Native OS style field and add PostOpen event, which pastes data into that field, than copies that data again and closes the form. As a result, clipboard will contain correct data, to paste into Notes style field

You can create simple hotspot near the required field, which opens form with one field and then pastes resulting data in target field.


Tuesday, 22 July 2014

Lotus Outline BUG

Found interesting bug in Lotus Notes, when outline is embedded in form and in some cases disappears from UI.
Simple experiment to reproduce the problem.
1) Create form
2) Create outline
3) Add button to form with code like @SetField("SomeField";"12345") or call ws.currentdocument.document.replaceitemvalue ("SomeField","1111")
4) Add outline to form
5) Save form.

Close Lotus Notes and delete cache.ndk from data directory.
Run Lotus Notes. Open created form in Lotus Notes client. Push the button - voila! Outline disappears!
Next time You open same form and push the button - it works fine (outline is in place).

The main problem is that, this only happens with non-cached outlines in cache.ndk. If You have 2 replicas on multiple servers, then opening each replica and pushing then button reproduces the problem again and again (possible recache for each replica?).

Workaround for this issue is, that You could use some form with all outlines included and loaded before main form - "cache" init form - it should load first, then the working form.

P.S. notes 7.0.3 has this bug I haven't found any workaround for it, but in 7.0.4 and 8+ works fine with cache form.

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.