Grant Bingham in his blog explains how to show users' pictures in Sametime 7.5 client. His step-by-step instructions with screenshots are much easier to follow than IBM's technote.
Technorati: Sametime
Wednesday, August 23, 2006
Tuesday, August 22, 2006
Avoid caching of web pages
To avoid caching of Domino web pages/documents when re-opening the page, put this code snippet into HTML Head Content property of the form:
"<meta http-equiv=\"Expires\" content=\"0\"> <meta http-equiv=\"Cache-Control\" content=\"must-revalidate\"> <meta http-equiv=\"Cache-Control\" content=\"no-cache\">"
"<meta http-equiv=\"Expires\" content=\"0\"> <meta http-equiv=\"Cache-Control\" content=\"must-revalidate\"> <meta http-equiv=\"Cache-Control\" content=\"no-cache\">"
Thursday, August 17, 2006
Programmatically export Lotus Notes contacts to VCard format
Lotus Notes 6 makes it possible to export contacts to VCard format. You can do it using File-Export menu. In the Export dialog box you can choose to export to VCard 2.1/3.0 format. After that you can import the resulting VCF file into Outlook.
But sometimes you would like to modify the format which the automated function uses. For example instead of "FirstName LastName/Company/Country" format in VCard's FN field you want to use simply "FirstName LastName", or you want to make a lookup to fetch the required field value.
"Export to VCard" option is available only in the databases of type "Domino Directory", you can not see that export option in regular Domino databases where you might have people documents.
Here is a LotusScript agent which runs on selected documents in any database and exports them to VCard fromat. The agent expects that the names of the fields in the documents are the same as in Address Book's "Person" form, so if you used other field names you must modify the agent. The agent's "Target" property is "All selected documents". Agent is run by choosing agent's name in the actions menu in a view after you've selected the documents you want to export.
'------------- AGENT START -----------
Sub Initialize
Dim session As New NotesSession
Dim nabdb As NotesDatabase
Dim nabdoc As NotesDocument
Dim nabcoll As NotesDocumentCollection
Dim vcardfile As String
Dim result As String
Dim linebreak As String
Dim fileNum As Integer
linebreak=Chr(13)+Chr(10)
vcardfile="c:\lotus_notes_contacts.vcf"
Set nabdb=session.CurrentDatabase
Set nabcoll=nabdb.UnprocessedDocuments
Print "Exporting"+Cstr(nabcoll.Count)+" documents to VCard "+vcardfile
If nabcoll.Count=0 Then Exit Sub
fileNum% = Freefile()
Open vcardfile For Output As #fileNum%
Set nabdoc=nabcoll.GetFirstDocument
While Not nabdoc Is Nothing
result="BEGIN:VCARD"+linebreak+ "VERSION:2.1"
result=result+linebreak+ "N:"+nabdoc.LastName(0)+";"+nabdoc.FirstName(0)+";"+nabdoc.MiddleName(0)+";"+nabdoc.Title(0)
If nabdoc.MiddleName(0)="" Then
FullName=nabdoc.FirstName(0)+" "+nabdoc.LastName(0)
Else
FullName=nabdoc.FirstName(0)+" "+nabdoc.MiddleName(0)+" "+nabdoc.LastName(0)
End If
result=result+linebreak+"FN:"+FullName
' result=result+linebreak+"NICKNAME:"+FullName
result=result+linebreak+"ORG:"+nabdoc.CompanyName(0)+";"+nabdoc.Department(0)
result=result+linebreak+"TITLE:"+nabdoc.JobTitle(0)
result=result+linebreak+"NOTE:"+nabdoc.Comment(0)
result=result+linebreak+"TEL;WORK;VOICE:"+nabdoc.OfficePhoneNumber(0)
result=result+linebreak+"TEL;HOME;VOICE:"+nabdoc.PhoneNumber(0)
result=result+linebreak+"TEL;CELL;VOICE:"+nabdoc.CellPhoneNumber(0)
result=result+linebreak+"TEL;WORK;FAX:"+nabdoc.OfficeFaxPhoneNumber(0)
result=result+linebreak+"TEL;HOME;FAX:"+nabdoc.HomeFAXPhoneNumber(0)
result=result+linebreak+"ADR;WORK:;"+nabdoc.OfficeCity(0)+";"+replaceLineBreak(nabdoc.OfficeStreetAddress(0))+";"+nabdoc.OfficeCity(0)+";"+nabdoc.OfficeState(0)+";"+nabdoc.OfficeZIP(0)+";"+nabdoc.OfficeCountry(0)
result=result+linebreak+"ADR;HOME:;;"+replaceLineBreak(nabdoc.StreetAddress(0))+";"+nabdoc.City(0)+";"+nabdoc.State(0)+";"+nabdoc.Zip(0)+";"+nabdoc.Country(0)
' result=result+linebreak+"URL;WORK:"+nabdoc.WebSite(0)
result=result+linebreak+"URL;HOME:"+nabdoc.WebSite(0)
result=result+linebreak+"PREF;INTERNET:"+nabdoc.InternetAddress(0)
result=result+linebreak+"EMAIL;INTERNET:"+nabdoc.MailAddress(0)
result=result+linebreak+"END:VCARD"
Print #fileNum%, result
Set nabdoc=nabcoll.GetNextDocument(nabdoc)
Wend
Close #fileNum%
End Sub
Function replaceLineBreak(oldString)
replaceFrom=Chr(13)+Chr(10)
replaceTo=", "
tmpString = Evaluate(|@ReplaceSubstring("| + oldString + |"; "|+replaceFrom+|"; "|+replaceTo+|")|)
replaceLineBreak=tmpString(0)
End Function
'------------- AGENT END -----------
Technorati: Show-n-Tell Thursday LotusScript VCard
But sometimes you would like to modify the format which the automated function uses. For example instead of "FirstName LastName/Company/Country" format in VCard's FN field you want to use simply "FirstName LastName", or you want to make a lookup to fetch the required field value.
"Export to VCard" option is available only in the databases of type "Domino Directory", you can not see that export option in regular Domino databases where you might have people documents.
Here is a LotusScript agent which runs on selected documents in any database and exports them to VCard fromat. The agent expects that the names of the fields in the documents are the same as in Address Book's "Person" form, so if you used other field names you must modify the agent. The agent's "Target" property is "All selected documents". Agent is run by choosing agent's name in the actions menu in a view after you've selected the documents you want to export.
'------------- AGENT START -----------
Sub Initialize
Dim session As New NotesSession
Dim nabdb As NotesDatabase
Dim nabdoc As NotesDocument
Dim nabcoll As NotesDocumentCollection
Dim vcardfile As String
Dim result As String
Dim linebreak As String
Dim fileNum As Integer
linebreak=Chr(13)+Chr(10)
vcardfile="c:\lotus_notes_contacts.vcf"
Set nabdb=session.CurrentDatabase
Set nabcoll=nabdb.UnprocessedDocuments
Print "Exporting"+Cstr(nabcoll.Count)+" documents to VCard "+vcardfile
If nabcoll.Count=0 Then Exit Sub
fileNum% = Freefile()
Open vcardfile For Output As #fileNum%
Set nabdoc=nabcoll.GetFirstDocument
While Not nabdoc Is Nothing
result="BEGIN:VCARD"+linebreak+ "VERSION:2.1"
result=result+linebreak+ "N:"+nabdoc.LastName(0)+";"+nabdoc.FirstName(0)+";"+nabdoc.MiddleName(0)+";"+nabdoc.Title(0)
If nabdoc.MiddleName(0)="" Then
FullName=nabdoc.FirstName(0)+" "+nabdoc.LastName(0)
Else
FullName=nabdoc.FirstName(0)+" "+nabdoc.MiddleName(0)+" "+nabdoc.LastName(0)
End If
result=result+linebreak+"FN:"+FullName
' result=result+linebreak+"NICKNAME:"+FullName
result=result+linebreak+"ORG:"+nabdoc.CompanyName(0)+";"+nabdoc.Department(0)
result=result+linebreak+"TITLE:"+nabdoc.JobTitle(0)
result=result+linebreak+"NOTE:"+nabdoc.Comment(0)
result=result+linebreak+"TEL;WORK;VOICE:"+nabdoc.OfficePhoneNumber(0)
result=result+linebreak+"TEL;HOME;VOICE:"+nabdoc.PhoneNumber(0)
result=result+linebreak+"TEL;CELL;VOICE:"+nabdoc.CellPhoneNumber(0)
result=result+linebreak+"TEL;WORK;FAX:"+nabdoc.OfficeFaxPhoneNumber(0)
result=result+linebreak+"TEL;HOME;FAX:"+nabdoc.HomeFAXPhoneNumber(0)
result=result+linebreak+"ADR;WORK:;"+nabdoc.OfficeCity(0)+";"+replaceLineBreak(nabdoc.OfficeStreetAddress(0))+";"+nabdoc.OfficeCity(0)+";"+nabdoc.OfficeState(0)+";"+nabdoc.OfficeZIP(0)+";"+nabdoc.OfficeCountry(0)
result=result+linebreak+"ADR;HOME:;;"+replaceLineBreak(nabdoc.StreetAddress(0))+";"+nabdoc.City(0)+";"+nabdoc.State(0)+";"+nabdoc.Zip(0)+";"+nabdoc.Country(0)
' result=result+linebreak+"URL;WORK:"+nabdoc.WebSite(0)
result=result+linebreak+"URL;HOME:"+nabdoc.WebSite(0)
result=result+linebreak+"PREF;INTERNET:"+nabdoc.InternetAddress(0)
result=result+linebreak+"EMAIL;INTERNET:"+nabdoc.MailAddress(0)
result=result+linebreak+"END:VCARD"
Print #fileNum%, result
Set nabdoc=nabcoll.GetNextDocument(nabdoc)
Wend
Close #fileNum%
End Sub
Function replaceLineBreak(oldString)
replaceFrom=Chr(13)+Chr(10)
replaceTo=", "
tmpString = Evaluate(|@ReplaceSubstring("| + oldString + |"; "|+replaceFrom+|"; "|+replaceTo+|")|)
replaceLineBreak=tmpString(0)
End Function
'------------- AGENT END -----------
Technorati: Show-n-Tell Thursday LotusScript VCard
Tuesday, August 15, 2006
Web charts from LotusScript
Thomas Adrian (www.notessidan.se) has now published a LotusScript agent which creates a simple column chart on a Web page by using div-tags . Chart's HTML/CSS code is created dynamically and the agent outputs it directly to the web browser.
Lotus Domino agent uses Apache Axis to consume a web service
Joachim Dagerot published an article on IBM developerWorks about how to use Apache Axis in a Domino Java agent to call a web service: Consuming Web services from a Lotus Domino Java agent. The article provides description and examples of how to call Web Services from your own Domino databases.
Last month I posted about another Java library to consume Web Services: WSIF (Web Services Invocation Framework). I had it as a stand-alone Java program in the Web Services tutorial published on DeveloperWorks, and last month I created a Domino Java agent re-using the same source code as in the stand-alone program. The problem with WSIF framework is that it is 3 years old, but it looks like there are plans for a new release.
What I liked with WSIF the most is the dynamic invocation of Web Services. That means that you do not need to create a stub or proxy or anything, you simply specify the location of the WSDL file, the function name and the parameters to the function:
java DynamicInvoker http://www.server.com/simplesoap/StockquoteSOAP.wsdl getQuote IBM
AXIS also has a DynamicInvoker example which gives absolutely the same result as DynamicInvoker WSIF example, but using different coding. So I guess that in most cases WSIF can be replaced by the newer AXIS. That would be interesting to compare the speed of initialization and processing between these two libraries. Considering that WSIF uses same Java libraries as AXIS, I would guess that they have the same speed... but you never know. So far the fastest (and smallest in size) library I tested was kSOAP, which was initially designed for mobile devices. And of course the good old deprecated MSSOAP (for VB/LotusScript), which is hard(impossible?) to compete with when it comes to dynamic invocation :)
From WSIF FAQ:
1.3 3. What are the differences between WSIF and Axis?
Axis is an implementation of SOAP. It includes on the server-side infrastructure for
deploying web service implementations and then routing SOAP messages between
clients and those implementations. It also implements the JAX-RPC specification for
invoking SOAP services. WSIF is similar to the client piece of Axis, in that it is used
for invoking services. However, WSIF's API is WSDL-driven and protocol
independent; it allows protocol-specific code ("providers") to be plugged in. For
invoking SOAP services, WSIF is in fact packaged with an Axis provider, that uses
Axis APIs (i.e. JAX-RPC) to do the invocation. So WSIF operates at a more abstract
level than Axis.
Last month I posted about another Java library to consume Web Services: WSIF (Web Services Invocation Framework). I had it as a stand-alone Java program in the Web Services tutorial published on DeveloperWorks, and last month I created a Domino Java agent re-using the same source code as in the stand-alone program. The problem with WSIF framework is that it is 3 years old, but it looks like there are plans for a new release.
What I liked with WSIF the most is the dynamic invocation of Web Services. That means that you do not need to create a stub or proxy or anything, you simply specify the location of the WSDL file, the function name and the parameters to the function:
java DynamicInvoker http://www.server.com/simplesoap/StockquoteSOAP.wsdl getQuote IBM
AXIS also has a DynamicInvoker example which gives absolutely the same result as DynamicInvoker WSIF example, but using different coding. So I guess that in most cases WSIF can be replaced by the newer AXIS. That would be interesting to compare the speed of initialization and processing between these two libraries. Considering that WSIF uses same Java libraries as AXIS, I would guess that they have the same speed... but you never know. So far the fastest (and smallest in size) library I tested was kSOAP, which was initially designed for mobile devices. And of course the good old deprecated MSSOAP (for VB/LotusScript), which is hard(impossible?) to compete with when it comes to dynamic invocation :)
From WSIF FAQ:
1.3 3. What are the differences between WSIF and Axis?
Axis is an implementation of SOAP. It includes on the server-side infrastructure for
deploying web service implementations and then routing SOAP messages between
clients and those implementations. It also implements the JAX-RPC specification for
invoking SOAP services. WSIF is similar to the client piece of Axis, in that it is used
for invoking services. However, WSIF's API is WSDL-driven and protocol
independent; it allows protocol-specific code ("providers") to be plugged in. For
invoking SOAP services, WSIF is in fact packaged with an Axis provider, that uses
Axis APIs (i.e. JAX-RPC) to do the invocation. So WSIF operates at a more abstract
level than Axis.
Saturday, August 12, 2006
Send a screenshot in Skype vs Sametime
Similar to "send a screenshot" feature in Sametime, now it is possible to send a screenshot in Skype too. Maybe Techsmith (the creator of the plugin) were inspired by that new cool feature in Sametime :)
It's not as seamless integrated as in Sametime and the picture is sent as a file instead of as an embedded image, but it's still usefull.
Read more here and see an instructional video.
Here is how it looks in Sametime:
Technorati: Skype Sametime
It's not as seamless integrated as in Sametime and the picture is sent as a file instead of as an embedded image, but it's still usefull.
Read more here and see an instructional video.
Here is how it looks in Sametime:
Technorati: Skype Sametime
Wednesday, August 09, 2006
Making simultaneous AJAX requests
During creation of Sametime Web Contact I had to create functionality which process simultaneously 2 web requests: the first request is continiously looping and fetching updates from server-side and the second request is triggered on-demand upon user's actions (send message, change status) and is executed only once. Both are done in asynchronous mode in order to not block the web browser.
Here is an example of how I implemented it:
<script>
var scriptpath="test2.html";
var default_parameters="par1=aaa&par2=bb&par3=ccc";
var stopLooping=false;
function initHTTP(){
var http_request = false;
if (window.XMLHttpRequest) { // Mozilla
http_request = new XMLHttpRequest();
}
else if (window.ActiveXObject) { // IE
try {
http_request = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
try {
http_request = new ActiveXObject("Microsoft.XMLHTTP");
} catch (e) {}
}
}
if (!http_request) {
alert('Your web browser is not compatible');
return false;
}
return http_request;
}
function makeAsyncRequestLoop(parameters) {
if(stopLooping==false){
if (parameters=="") parameters=default_parameters;
var http_request1=initHTTP();
http_request1.onreadystatechange = function() { readyhandler(http_request1, true); };
http_request1.open('POST', scriptpath, true);
http_request1.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
http_request1.setRequestHeader("Content-length", parameters.length);
http_request1.setRequestHeader("Connection", "Keep-Alive");
http_request1.send(parameters);
}
}
function makeAsyncRequestOnce(parameters){
var http_request2=initHTTP();
http_request2.onreadystatechange = function() { readyhandler(http_request2, false); };
http_request2.open('POST', scriptpath, true);
http_request2.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
http_request2.setRequestHeader("Content-length", parameters.length);
http_request2.setRequestHeader("Connection", "Keep-Alive");
http_request2.send(parameters);
}
function readyhandler(http_requestobj, fetchloop) {
if (http_requestobj.readyState == 4){
if (http_requestobj.status == 200) {
document.getElementById("test").innerHTML=(new Date().getTime())+" "+http_requestobj.responseText;
if(fetchloop==false) alert((new Date().getTime())+" "+http_requestobj.responseText)
}
if(fetchloop==true) {
makeAsyncRequestLoop(default_parameters); //request same web page again
}
}
}
function startLoop(){
stopLooping=false;
makeAsyncRequestLoop(default_parameters);
}
function stopLoop(){
stopLooping=true;
}
function customRequest(param){
makeAsyncRequestOnce(param);
}
</script>
<input type="Button" name="Start" value="Start" onClick="startLoop()">
<input type="Button" name="Stop" value="Stop" onClick="stopLoop()"><br><br>
<input type="Button" name="Custom" value="Custom" onClick="customRequest('p1=abc&p2=cde')">
<br><br><br>
<div id="test"></div>
If you test the code, you will see that the fetch loop continues getting new data even when the alert box is being shown.
Warning: IE and Firefox can handle only 2 concurrent connections to the same server. The third connection will get queued until one of the previous connections are released. So if you send 2 requests which take 2 minutes to accomplish, the third request will be queued for 2 minutes until processed. This little "feature" took me 2 days to realize.
Technorati: Show-n-Tell Thursday
Here is an example of how I implemented it:
<script>
var scriptpath="test2.html";
var default_parameters="par1=aaa&par2=bb&par3=ccc";
var stopLooping=false;
function initHTTP(){
var http_request = false;
if (window.XMLHttpRequest) { // Mozilla
http_request = new XMLHttpRequest();
}
else if (window.ActiveXObject) { // IE
try {
http_request = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
try {
http_request = new ActiveXObject("Microsoft.XMLHTTP");
} catch (e) {}
}
}
if (!http_request) {
alert('Your web browser is not compatible');
return false;
}
return http_request;
}
function makeAsyncRequestLoop(parameters) {
if(stopLooping==false){
if (parameters=="") parameters=default_parameters;
var http_request1=initHTTP();
http_request1.onreadystatechange = function() { readyhandler(http_request1, true); };
http_request1.open('POST', scriptpath, true);
http_request1.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
http_request1.setRequestHeader("Content-length", parameters.length);
http_request1.setRequestHeader("Connection", "Keep-Alive");
http_request1.send(parameters);
}
}
function makeAsyncRequestOnce(parameters){
var http_request2=initHTTP();
http_request2.onreadystatechange = function() { readyhandler(http_request2, false); };
http_request2.open('POST', scriptpath, true);
http_request2.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
http_request2.setRequestHeader("Content-length", parameters.length);
http_request2.setRequestHeader("Connection", "Keep-Alive");
http_request2.send(parameters);
}
function readyhandler(http_requestobj, fetchloop) {
if (http_requestobj.readyState == 4){
if (http_requestobj.status == 200) {
document.getElementById("test").innerHTML=(new Date().getTime())+" "+http_requestobj.responseText;
if(fetchloop==false) alert((new Date().getTime())+" "+http_requestobj.responseText)
}
if(fetchloop==true) {
makeAsyncRequestLoop(default_parameters); //request same web page again
}
}
}
function startLoop(){
stopLooping=false;
makeAsyncRequestLoop(default_parameters);
}
function stopLoop(){
stopLooping=true;
}
function customRequest(param){
makeAsyncRequestOnce(param);
}
</script>
<input type="Button" name="Start" value="Start" onClick="startLoop()">
<input type="Button" name="Stop" value="Stop" onClick="stopLoop()"><br><br>
<input type="Button" name="Custom" value="Custom" onClick="customRequest('p1=abc&p2=cde')">
<br><br><br>
<div id="test"></div>
If you test the code, you will see that the fetch loop continues getting new data even when the alert box is being shown.
Warning: IE and Firefox can handle only 2 concurrent connections to the same server. The third connection will get queued until one of the previous connections are released. So if you send 2 requests which take 2 minutes to accomplish, the third request will be queued for 2 minutes until processed. This little "feature" took me 2 days to realize.
Technorati: Show-n-Tell Thursday
Tuesday, August 08, 2006
Address Book on server? Nah, not for l33t D0m1n0 developerz!
I have found a rather old but still interesting article in The View written by Page Nix. The article shows how Domino applications can have it's own "Address Book" for validating user's login. The main usage area of the application is in web databases where database responsible person wants to quickly add new users and there is no possibility to add these new users to server's (secondary) Address Book. The responsible person can simply create a new user directly in the database.LotusScript agent is used to verify if the login credentials provided by user match the credentials saved in the user document, and if they match the agent sets a cookie to user's browser containing a "session id".
So far I could not find any obvious security risks with this approach. Groups and ACL Roles will not work without additional programming, but in many cases it's not needed.
Demo application is included in the article.
So far I could not find any obvious security risks with this approach. Groups and ACL Roles will not work without additional programming, but in many cases it's not needed.
Demo application is included in the article.
Tuesday, August 01, 2006
White nights in Stockholm
In the middle of June you can experience white nights in Stockholm. It's dark for only 2.5 hours: from 00 to 02:30 o'clock. Even when it's gets dark, it never gets compeletely dark, you can without problems see everything as if it was 23 o'clock and not kl 01. I think that 21th June is the longest daylight day of the year, so I was 1 day late.
I have a LotusScript application which interacts with webcamera using Windows API and can take pictures and movies at scheduled times. So I put the webcam on the balcony for the whole night 22 June-23 June and recorded the process of getting dark outside and then getting light again.
As the webcam does not work well when it's dark, I made also a reference shot at 01 o'clock (the darkest time) with an ordinary digital camera.
Here is a Flash movie I composed from the taken images. Click the image to open the movie.
Here is a reference shot at 01 o'clock (click to view full size):
P.S. I will later publish the Lotus Notes application I used for taking webcam pictures. It can be used to spy on your Lotus Notes users if they have a webcam :)
I have a LotusScript application which interacts with webcamera using Windows API and can take pictures and movies at scheduled times. So I put the webcam on the balcony for the whole night 22 June-23 June and recorded the process of getting dark outside and then getting light again.
As the webcam does not work well when it's dark, I made also a reference shot at 01 o'clock (the darkest time) with an ordinary digital camera.
Here is a Flash movie I composed from the taken images. Click the image to open the movie.
Here is a reference shot at 01 o'clock (click to view full size):
P.S. I will later publish the Lotus Notes application I used for taking webcam pictures. It can be used to spy on your Lotus Notes users if they have a webcam :)
Subscribe to:
Posts (Atom)