Pages

Thursday, December 31, 2009

CDATA Tag in XML

It was once that I had to export the contents of all the (Action) Buttons in a lotus notes application into a DXL file, ( a Domino XML file) and parse them back to create Agents containing the same code as present in the Buttons.

The operation ran smoothly until I encountered a specific issue. And the issue was with certain characters like "<", ">", etc...

To give you a clear picture, let me say that I have a button named Source

The code in that button be,

...code fragment...
if(x<y) then
  ...do some operation
else if (x>y) then
   do some thing else
end if
...code fragment...

So my exporter shall export it into a DXL as follows (say)

<dxl>
 <buttons>
  <button>
   <name> Source </name>
   <code>
    ...code fragment...
    if(x<y) then
      ...do some operation
    else if (x>
y) then
       do some thing else
    end if
    ...code fragment...
   </code>
  <button>
 </buttons>
</dxl>


When I parse this resultant file to obtain the code, the code that has been highlighted in bold was considered as a tag accoring to the simple plain xml rule... any thing inbetween < and > is considered as a tag. And that created a lot of trouble for me when I attempted to parse the file.

So, eventually I ended up searching for a solution and ended up by discovering the usage of the CDATA tag.

Any thing that is put in between a CDATA tag is not parsed and thus it prevented my dxl from breaking up.

The bug fixed code will look like the following,

<dxl>
 <buttons>
  <button>
   <name> Source </name>
   <code>
    <![CDATA[
     ...code fragment...
     if(x<y) then
       ...do some operation
     else if (x>y) then
        do some thing else
     end if
     ...code fragment...
    ]]>

   </code>
  <button>
 </buttons>
</dxl>

Monday, December 28, 2009

Display a HTML element using javascript - A cross browser solution

function hideElement(divId) {
var element;
// get the element referenced by the parameter elementID
if (typeof elementID === "string") {
element = document.getElementById(divId);
} else {
element = divId
}

if ((typeof element == 'undefined') || (element == null)) {
throw new Error(
'No element with id "' + divId + '" is found
- in function hideElement(divId)');
}
tabContent.style.display = 'none';
}

Hide A HTML Element Using Javascript - A Cross Browser Solution

function hideElement(divId) {
var element;
// get the element referenced by the parameter elementID
if (typeof elementID === "string") {
element = document.getElementById(divId);
} else {
element = divId
}

if ((typeof element == 'undefined') || (element == null)) {
throw new Error(
'No element with id "' + divId + '" is found
- in function hideElement(divId)');
}
element.style.display = 'none';
}

Set value to a combobox - Javascript

/***********************************************************************************
@Author : Karthikeyan A
@Purpose : To set a particular value to a combo box, Provided the value is one of its options
@Type : Function
@Name : setComboValue
@Param : comboBoxId, ID of the combo box element
@Param : ValueToSet, The value to be set to the combo box
***********************************************************************************/
function setComboValue(comboBoxId,valueToSet){
var cmbElement=document.getElementById(comboBoxId);
if((typeof cmbElement=='undefined') || (cmbElement==null) || (typeof valueToSet=='undefined')) {
throw new Error('No element with id "'+ comboBoxId + '" is found <br> - in function getComboValue(comboBoxID)');
}
var cmbItr;
try {
for (cmbItr=0;cmbItr<cmbElement.options.length;cmbItr++) {
var currValue=document.all?cmbElement.options[cmbItr].text:cmbElement.options[cmbItr].value;
if (currValue==valueToSet) {
cmbElement.options.selectedIndex=cmbItr;
}
}
} catch(notCombobox) {
try {
cmbElement.value=valueToSet;
} catch (notFieldElement) {
throw new Error("The element corresponding to the id:\""+ comboBoxID+"\""+
" is neither a Combo Box, nor an Input Field or does not exist"+
"<br> - in function getComboValue(comboBoxID)");
}
}
}

Method to remove duplicate values in Array lists

/**
* Method to remove duplicate values in Array lists
* @param arrayList- The list from which the duplicates needs to be removed
* @return ArrayList
* @author karthikeyan_a
* @created 11-May-2009
*/
public ArrayList removeDuplicates(ArrayList arrayList) {

//Create a HashSet which allows no duplicates
HashSet hashSet = new HashSet(arrayList);

//Assign the HashSet to a new ArrayList
ArrayList resultArrayList = new ArrayList(hashSet) ;

//Ensure correct order, since HashSet doesn't
Collections.sort(resultArrayList);

return resultArrayList;
}

List Servers Indicated in Connection documents if Names.nsf

/**
* Method to return a list of server names present in the connection documents in names.nsf
* @param session- The Notes Session belonging to a particular user
* @return String Array
* @author karthikeyan_a
* @created 11-May-2009
* @see ArrayList removeDuplicates(ArrayList arrayList) ----> http://ozinisle.blogspot.com/2009/12/method-to-remove-duplicate-values-in.html
*/

public String[] getServerNames(Session session){
//initializing return type
String[] serverNames=null;
try {
//declaring variables and objects necessary for further manipulations
ArrayList serverNameList=new ArrayList();

Database addBook=null;
View connectionView=null;
ViewEntryCollection connectionEntries=null;
ViewEntry connectionEntry=null;
Document connectionDocument=null;

String connectionServerName=null;

int entryCount=0;
int serverNameCount=0;

//getting the handle for the names and address book in the local server
addBook=session.getDatabase("", "names.nsf");

//if the handle is not set then return the same
if (addBook==null) {
System.out.println("Names.nsf is not found");
return null;
}

// if the address book is not open before then open it again
if (!addBook.isOpen()) {
addBook.open();
}

//get the handle of the view which has the list of various connection documents in names.nsf
connectionView=addBook.getView("Adva_nced\\Connections");

//if the handle for the same is not set then return null
if (connectionView==null) {
System.out.println("Connections view is not found in names.nsf");
return null;
}

//get the handle of the collection of all entries present in the view
connectionEntries=connectionView.getAllEntries();

//if there are no entries found then return null
if (connectionEntries.getCount()==0) {
System.out.println("There are no connection documents found in names.nsf");
return null;
}
System.out.println("There are "+connectionEntries.getCount()+" connection documents found in names.nsf");

//set the handle for the first entry in the collection
connectionEntry=connectionEntries.getFirstEntry();

//loop through the entries in the collection and get the destination server's name
for (entryCount=0;entryCount<connectionEntries.getCount();entryCount++) { //start of entry collection for loop

//if the concerned entry is a document then proceed else skip
if (connectionEntry.isDocument()) {

//get the handle of the document associated with the current entry
connectionDocument=connectionEntry.getDocument();

//get the name of the server the connection document is associated with
connectionServerName=connectionDocument.getItemValueString("Destination");

//put that in the list of server names found so far
serverNameList.add(connectionServerName);
}

//proceed to the next entry in the collection
connectionEntry=connectionEntries.getNextEntry(connectionEntry);
} //end of entry collection for loop

//remove any duplicate entries present in the server list
serverNameList=removeDuplicates(serverNameList);

//convert the array list into a string array
serverNames=new String[serverNameList.size()];
for (serverNameCount=0;serverNameCount<serverNameList.size();serverNameCount++) {
serverNames[serverNameCount]=serverNameList.get(serverNameCount).toString();
}

} catch (NotesException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

//return the resultant string array
return serverNames;
}

Custom Alert box in Java

/**
* Method to mimic the message box in lotus script/ javascript
* @param message- message to be displayed in the message box
* @param title- title to be displayed in the message box
* @return void
* @author Karthikeyan_A
* @since 04-May-2009
*/


public void msgbox(String message,String title) {

Object[] options = {"Ok"};
javax.swing.JFrame frame=new javax.swing.JFrame();
int n = javax.swing.JOptionPane.showOptionDialog(null,
message,
title,
javax.swing.JOptionPane.OK_OPTION,
javax.swing.JOptionPane.PLAIN_MESSAGE,
null,
options,
options[0]);
}

Capture on exit event of a TextField/JTextField in Java

/*==============================================================================
* Author : Karthikeyan A - MaargaSystems pvt.;td
* Created : 04-May-2009
* Purpose : illustrate the usage of on FoucsListener Class by Capturing on exit event of a TextField/JTextField
*==============================================================================
*/

import java.awt.BorderLayout;
import java.awt.Panel;
import java.awt.TextField;

import java.awt.Frame;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;

//import javax.swing.*;
//import java.awt.*;
//import java.awt.event.*;
public class TextFieldTask {

public void onExit_TextField(TextField tf) {
tf.addFocusListener(new FocusListener(){
public void focusGained(FocusEvent arg0) {
System.out.println("gained focus");
}

public void focusLost(FocusEvent arg0) {
System.out.println("lost focus");
msgbox("lost focus","captured on exit");

}
});
}

void msgbox(String message,String title) {

Object[] options = {"Ok"};
javax.swing.JFrame frame=new javax.swing.JFrame();
int n = javax.swing.JOptionPane.showOptionDialog(frame,
message,
title,
javax.swing.JOptionPane.OK_OPTION,
0,
null,
options,
options[0]);
}

public static void main(String args[]){
final TextFieldTask tft=new TextFieldTask();
//JPanel jp=new JPanel();
//JTextField tf1=new JTextField();
//JTextField tf2=new JTextField();

Panel p=new Panel();
TextField tf1=new TextField();
TextField tf2=new TextField();

tft.onExit_TextField(tf1);

//jp.setLayout(new BorderLayout());
//jp.add(tf1,BorderLayout.NORTH);
//jp.add(tf2,BorderLayout.SOUTH);
p.setLayout(new BorderLayout());
p.add(tf1,BorderLayout.NORTH);
p.add(tf2,BorderLayout.SOUTH);

//JFrame jf= new JFrame("Test");
//jf.add(jp);
//jf.setSize(100,100);
//jf.setVisible(true);
//jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

Frame f=new Frame("test");
f.add(p);
f.setSize(100,100);
f.setVisible(true);

}
}

Create a Vector from a string array

public java.util.Vector createVectorFromStringArray( String items[]){
java.util.Vector v=new java.util.Vector();
int itr=0;
for(itr=0;itr<items.length;itr++){
v.add(items[itr]);
}
return v;
}

Method to assimilate the contents of a string Array List into a string array

<i>/**
* Method to assimilate the contents of a string Array List into a string array
* @param arrList - an array list with string objects
* @return String[]
* @author karthikeyan_a
* @since 30-April-2009
*/</i>
public String[] arrayListToStringArray(ArrayList arrList){
String[] strArray=null;
Object[] elements=arrList.toArray();
strArray=new String[elements.length];
int countItr=0;
for (countItr=0;countItr<elements.length;countItr++){
strArray[countItr]=elements[countItr].toString();
}
//recycle objects
elements=null;
arrList=null;

return strArray;
}

Get All Documents from a View

/**
* Method to obtain a collection of all documents from a specified view in a specified database
* @param targetDB - the database from which the document collection is to be obtained
* @param viewName - the name of the view from which the document collection is to be obtained
* @return String[]
* @author karthikeyan_a
* @since 30-April-2009
*/

public DocumentCollection getViewDocuments(Database targetDB, String viewName) {
//if any of the input parameters in undefined then return null
if ((targetDB==null)|| (viewName==null) || (viewName.trim().equals(""))) {
return null;
}
//mark the return value and initialize other variables
DocumentCollection viewDocs=null;
Document viewDoc=null;
ViewEntryCollection viewEntries=null;
ViewEntry viewEntry=null;
View chosenView=null;
try {
//set the handle for the view mentioned by the view name
chosenView=targetDB.getView(viewName);
//if the view handle is not set then return null
if (chosenView==null) {
return null;
}

//ensure that an empty document collection is created
Random generator = new Random();
viewDocs=chosenView.getAllDocumentsByKey(".~^$#$^&~."+generator.toString());

/**
* loop through all the entries in the view and push their associated
* documents into the document collection
*/
viewEntries= chosenView.getAllEntries();
//get the handle for the first entry in the collection
viewEntry=viewEntries.getFirstEntry();
while(viewEntry!=null) {
if (viewEntry.isDocument()) {
viewDoc=viewEntry.getDocument();
/*
* If the document concerned with the entry is already present in the
* collection then a duplicate exception is thrown. So catch the same and
* dont allow that to hurt the process
*/
try {
viewDocs.addDocument(viewDoc);
}
catch (NotesException duplicateException) {
//by pass exception
duplicateException=null;
}
}
//push the handle to the next entry in the collection
viewEntry=viewEntries.getNextEntry(viewEntry);
}

} catch (NotesException e) {
e.printStackTrace();
}
//return the collection of documents thus obtained
return viewDocs;
}

Get the names of all databases to which you have access to

/**
* Method to get the names of all databases to which you have access to
* @param : serverName- name of the server from which the database names needs to be retrieved
* @param : userName- name of the user having access to
* @return : boolean
* @see : boolean checkAccessToDatabase(Database targetDB, String userName) ---> http://ozinisle.blogspot.com/2009/12/check-access-to-lotusnotes-database.html
*/

public Vector getAllDatabases(Session session, String serverName,String userName) {
Vector allDbs = new Vector();
int incre = 0 ;
int countedDbs=0;
try {
DbDirectory dir = session.getDbDirectory(serverName);
Database db = dir.getFirstDatabase(DbDirectory.TEMPLATE_CANDIDATE);
while (db != null) {
incre = incre + 1;
if (checkAccessToDatabase(db,userName)) {
countedDbs++;
allDbs.add(db.getFilePath());
}
db = dir.getNextDatabase();
}
//System.out.println("You have access to "+countedDbs+" out of "+incre+ " databases");
session.recycle();
} catch(Exception e) {
e.printStackTrace();
}
return allDbs;
}

Check Access To A LotusNotes Database

/**
* Method to check wether a particular user has access to a provided database
* @param : targetDB- database to which access permissions needs to be checked
* @param : userName- name of the user whose access level is to be checked
* @return : boolean
*/

public boolean checkAccessToDatabase(Database targetDB, String userName) {
boolean accessFlag=false;
int accessLevel=0;
if (targetDB==null){
return accessFlag;
}
try {
if(!targetDB.isOpen()) {
targetDB.open();
}
accessLevel=targetDB.queryAccess(userName);
} catch (NotesException e) {
return accessFlag;
}

if (accessLevel>0){
accessFlag=true;
}

return accessFlag;
}

Method to return the names of a particular set or all design elements from a databas

/**
* Method to return the names of a particular set or all design elements from a database
* depending upon the user's input
* @param targetDB- The database from which the design elements needs to be obtained
* @param DESIGN_ELEMENT_TYPE - An integer representation of the design element type
* @return String[]
* @author karthikeyan_a
* @since 30-April-2009
* @see NotesCollection selectDesigns(Database targetDB,int DESIGN_ELEMENT_TYPE) ---> http://ozinisle.blogspot.com/2009/12/select-specific-design-elements-from.html
* @see ArrayList filterHiddenElements(ArrayList designElementNames) ---> http://ozinisle.blogspot.com/2009/12/remove-names-of-hidden-elements-from.html
* @see ArrayList removeAlias(ArrayList designElementNames) ---> http://ozinisle.blogspot.com/2009/12/remove-alias-names-from-design-element.html
* @see String[] CreateStringArrayFromVector(ArrayList stringVector) ---> http://ozinisle.blogspot.com/2009/12/create-string-array-from-vector.html
*/

public String[] getDesignElementNames(Database targetDB,int DESIGN_ELEMENT_TYPE) {

if (targetDB==null) return null;

String noteID=null;
String noteIDTemp=null;
Document dsgnDoc=null;
NoteCollection nc=null;
ArrayList designElementNames=null;
//initialize the return value
String[] designElementNamesString=null;

try {
//create an empty note collection
nc =selectDesigns(targetDB,DESIGN_ELEMENT_TYPE);
//initiate return value
designElementNames=new ArrayList();

if (nc.getCount()>0){
noteID=nc.getFirstNoteID();
//check if the noteID is neither empty nor null and get the design doc's name associated with the id
while (noteID!=null && !noteID.equals("")) { //start of noteID while
try {
noteIDTemp = noteID;
dsgnDoc=targetDB.getDocumentByID(noteIDTemp);
//add the names of the design elements into the vector
designElementNames.add(dsgnDoc.getItemValueString("$TITLE"));
noteID=nc.getNextNoteID(noteID);
} catch (NotesException ne) {
ne.printStackTrace();
}
} //end of noteID while
}
} catch (NotesException e) {
e.printStackTrace();
}

// remove alias names of design elements
designElementNames=removeAlias(designElementNames);
//filter hidden design elements
designElementNames=filterHiddenElements(designElementNames);
//create a string array of the resultant design elements name
designElementNamesString=arrayListToStringArray(designElementNames);

//recycle objects
noteID=null;
nc=null;
noteIDTemp=null;
dsgnDoc=null;
targetDB=null;
designElementNames=null;

//return the array of names
return designElementNamesString;
} //end of function::getDesignElementNames

Select specific design elements from a Lotus Notes database

/**
* Method to return a particular set or all design elements from a database
* depending upon the user's input
* @param targetDB-The database from which the design elements needs to be obtained
* @param DESIGN_ELEMENT_TYPE - An integer representation of the design element type
* @return NoteCollection
* @author karthikeyan_a
* @since 29-April-2009
*/

public NoteCollection selectDesignElements(Database targetDB,int DESIGN_ELEMENT_TYPE) throws NotesException {

//declaration of constants
int ACL=1;
int ACTIONS=2;
int AGENTS=3;
int DATABASE_SCRIPT=4;
int DATA_CONNECTIONS=5;
int DOCUMENTS=6;
int FOLDERS=7;
int FORMS=8;
int FRAMESETS=9;
int HELP_ABOUT=10;
int HELP_INDEX=11;
int HELP_USING=12;
int ICON=13;
int IMAGE_RESOURCES=14;
int JAVA_RESOURCES=15;
int MISC_CODE_ELEMENTS=16;
int MISC_FORMAT_ELEMENTS=17;
int MISC_INDEX_ELEMENTS=18;
int NAVIGATORS=19;
int OUTLINES=20;
int PAGES=21;
int PROFILES=22;
int REPLICATION_FORMULAS=23;
int SCRIPT_LIBRARIES=24;
int SHARED_FIELDS=25;
int STYLE_SHEET_RESOURCES=26;
int SUB_FORMS=27;
int VIEWS=28;

NoteCollection nc=null;
nc = targetDB.createNoteCollection(false);
switch (DESIGN_ELEMENT_TYPE) {
case 1:
nc.setSelectAcl(true);
break;
case 2:
nc.setSelectActions(true);
break;
case 3:
nc.setSelectAgents(true);
break;
case 4:
nc.setSelectDatabaseScript(true);
break;
case 5:
nc.setSelectDataConnections(true);
break;
case 6:
nc.setSelectDocuments(true);
break;
case 7:
nc.setSelectFolders(true);
break;
case 8:
nc.setSelectForms(true);
case 9:
nc.setSelectFramesets(true);
break;
case 10:
nc.setSelectHelpAbout(true);
break;
case 11:
nc.setSelectHelpIndex(true);
break;
case 12:
nc.setSelectHelpUsing(true);
break;
case 13:
nc.setSelectIcon(true);
break;
case 14:
nc.setSelectImageResources(true);
break;
case 15:
nc.setSelectJavaResources(true);
break;
case 16:
nc.setSelectMiscCodeElements(true);
break;
case 17:
nc.setSelectMiscFormatElements(true);
break;
case 18:
nc.setSelectMiscIndexElements(true);
break;
case 19:
nc.setSelectNavigators(true);
break;
case 20:
nc.setSelectOutlines(true);
break;
case 21:
nc.setSelectPages(true);
break;
case 22:
nc.setSelectProfiles(true);
break;
case 23:
nc.setSelectReplicationFormulas(true);
break;
case 24:
nc.setSelectScriptLibraries(true);
break;
case 25:
nc.setSelectSharedFields(true);
break;
case 26:
nc.setSelectStylesheetResources(true);
break;
case 27:
nc.setSelectSubforms(true);
break;
case 28:
nc.setSelectViews(true);
break;
default: nc.selectAllNotes(true);
}
//build the design document collection of the resultant collection
nc.buildCollection();
//return the design collection
return nc;
} //end of function::selectDesigns

Create String Array from Vector

/**
* Method to assimilate the contents of a string vector (Vector) into a string array
* @param : stringVector - a Vector with String Objects
* @return String[]
*/

public String[] CreateStringArrayFromVector(Vector stringVector) {
//if the input parameter is a null value then return null
if (stringVector== null) return null;
//initialize the return value
String returnValue[]=new String[stringVector.size()];
Enumeration e=stringVector.elements();
int count=0;
//push the contents of the vector into a string array
while (e.hasMoreElements()) {
returnValue[count]=(String)e.nextElement();
count++;
}
return returnValue;
} //end of function::CreateStringArrayFromVector

Remove Names of hidden elements from a list of names of design elements

/**
* Method to remove the strings from a string ArrayList <String>) which corresponds
* to the name of an hidden element
* @param designElementNames - an ArrayList with String Objects
* @return ArrayList
* @author karthikeyan_a
* @since 30-April-2009
*/

public ArrayList filterHiddenElements(ArrayList designElementNames) {
//if the input param in null then return null
if (designElementNames==null)return null;
//initialize the return value
ArrayList filteredDsgnElements=new ArrayList();
String dsgnName=null;
Object[] buffer=designElementNames.toArray();
int countItr=0;
/**
* loop through all the string objects in the input vector and remove the string objects
* that correspond to the name of an hidden design element
*/
for(countItr=0;countItr<buffer.length;countItr++) {
dsgnName=buffer[countItr].toString().trim();
if ( !( (dsgnName.indexOf("(")==0) && (dsgnName.indexOf(")")==(dsgnName.length()-1)) ) ) {
filteredDsgnElements.add(dsgnName);
}
}
//recycle objects
dsgnName=null;
designElementNames=null;

return filteredDsgnElements;
} //end of function::filterHiddenElements

Remove Alias Names from Design Element Names - Java

/**
* Method to remove the part of strings from string objects in a string vector (Vector<String>)
* which correspond to the alias names of the design elements
* @param designElementNames - an ArrayList with String Objects
* @return ArrayList
* @author Karthikeyan_a
* @since 30-April-2009
*/

public ArrayList removeAlias(ArrayList designElementNames) {
//if the input param in null then return null
if (designElementNames==null)return null;
//initialize the return value
ArrayList filteredDsgnElements=new ArrayList();
String dsgnName=null;
Object[] buffer=designElementNames.toArray();
int countItr=0;
/**
* loop through all the string objects in the input vector and remove the part of stirngs in
* string objects that correspond to the alias names of the design elements
*/
for(countItr=0;countItr<buffer.length;countItr++) {
dsgnName=buffer[countItr].toString().trim();
if(dsgnName.indexOf("|")==-1) {
filteredDsgnElements.add(dsgnName);
} else {
filteredDsgnElements.add(dsgnName.substring(0, dsgnName.indexOf("|")).trim());
}
}
//recycle objects
dsgnName=null;
designElementNames=null;

return filteredDsgnElements;
} //end of function::removeAlias

Determining If a String Contains a Substring - Java

String string = "Madam, I am Adam";

// Starts with
boolean b = string.startsWith("Mad"); // true

// Ends with
b = string.endsWith("dam"); // true

// Anywhere
b = string.indexOf("I am") > 0; // true

// To ignore case, regular expressions must be used

// Starts with
b = string.matches("(?i)mad.*");

// Ends with
b = string.matches("(?i).*adam");

// Anywhere
b = string.matches("(?i).*i am.*");

Trim() Function in Javascript

function trim(str, chars) {
return ltrim(rtrim(str, chars), chars);
}

function ltrim(str, chars) {
chars = chars || "\\s";
return str.replace(new RegExp("^[" + chars + "]+", "g"), "");
}

function rtrim(str, chars) {
chars = chars || "\\s";
return str.replace(new RegExp("[" + chars + "]+$", "g"), "");
}

Get Selected Value from Combo box

Following is a function that will help you retrieve the value selected in a HTML Combobox.

function getComboValue(comboBoxID) {
var comboObj=document.getElementById(comboBoxID);
var comboValue=document.all?comboObj.options[comboObj.selectedIndex].text:comboObj.options[comboObj.selectedIndex].value;
return (trim(comboValue,"\n"));
}

function trim(str, chars) {
return ltrim(rtrim(str, chars), chars);
}

function ltrim(str, chars) {
chars = chars || "\\s";
return str.replace(new RegExp("^[" + chars + "]+", "g"), "");
}

function rtrim(str, chars) {
chars = chars || "\\s";
return str.replace(new RegExp("[" + chars + "]+$", "g"), "");
}

Thursday, December 24, 2009

MEDIA attribute of Link Tag - A solution to print Webpages

As commonly found in most of the web applications, I was ought to add a "Print" feature. This is where a problem started. When ever I attempt to print, I should hide unwanted sections like, Menus, Title Images, comment sections, calendars etc.

So I got into the tedious process of hiding all the unwanted portions and aligning the rest with the page, print them and get the application back to its normal GUI, which was not welcome. It was then I discovered this gorgeous MEDIA thing in link tags.

Now what It helps me to do is, the same tedious process, in a user friendly way.

I create a separate CSS which I want to apply when the page is about to be printed.
Let me call it as "Style2.css". My existing page uses a CSS named "Style1.css". In this case what I should do is add the following link tag to my HTML Head tag.

For IE6 and IE7:

<link rel="stylesheet" href="Style2.css" type="text/css" media="screen">
<link rel="stylesheet" href="Style1.css" type="text/css" media="print">


For IE8, Firefox, Opera and Safari:

<link rel="stylesheet" type="text/css" media="print" href="Style1.css" media="screen" href="Style2.css" />


Now have a button any where in your page and which says "window.print()"

I beauty is when you click that button, The Page on the browser does not change where as , the print outs that you receive will be of the page with styles governed by Style2.css

If I am not clear, please try this out.
1. Create a web page,
2. put some div tags with text contents,
3. apply styles using a style sheet , say Style1.css
4. create a new style sheet, say style2.css and hide a few div tags
5. Put the following in the head tag of your html page

For IE6 and IE7:

<link rel="stylesheet" href="Style2.css" type="text/css" media="screen">
<link rel="stylesheet" href="Style1.css" type="text/css" media="print">


For IE8, Firefox, Opera and Safari:

<link rel="stylesheet" type="text/css" media="print" href="Style1.css" media="screen" href="Style2.css" />


6. Add a button in your page with a label, say "Print" and in its on click event add the javascript code "window.print()"
7. Now print the page.
8. You will be able to see that the div tags that you have hidden using the style sheet "Style2.css" has not appeared on the print out where as the browser screen remains the same

What if @DBColumn Fails on web

There was one scenario in which I had to populate a combo box (drop down) which contains all the values in the first column of a view.

But the data in the view is so large that the formula itself failed. So I had to think of other work arounds to do the same.

I tried changing the "Number of lines to display in view" in the "Domino Web Engine" Tab from 1000 to 5000 in the server document. This resulted in an other error that caused my "openView?readviewentries" url to collapse and bomb an error. Probably 'cos of a heap over flow sort of thing.

So I required a more stable solution and hence arrived at a result. Many might feel that this one is quite inefficient as I do. The problem is if you have 50000 documents in that view, this code shall ask the browser to hit the server 50 times to get all the data as read view entries by default shall return only 1000 documents and since I have experienced it, I dont recommend changing it.

Please correct me if I am wrong

The following is a fragment of code that I developed to build the combo box.
It is not so generic and hence you may have to tweak it a bit to suit your requirements

/************************************/
var MAX_DOCS_IN_THOUSANDS=50; //This means you get up to 50000 options in your combobox

var comboBox="<select id='myComboID' onchange='javascript:myOnchangeEvent()'>";

//read view entries by default shall return xml entries of a maximum of 1000 documents
//hence loop through all the documents in the view in sets of 1000s and get all the company names
for (itr=1; itr<=MAX_DOCS_IN_THOUSANDS; itr++) { //start of for loop
startIndex=(itr-1)*1000+1;
try {
response=getViewColumn(DBServer,DBPath,viewName,startIndex);
} catch (error) {
info=" Probably the server name or the path of the database that you have mentioned is wrong </b>";
break;
}

var textNodes=response.getElementsByTagName("text");
try {
if (textNodes.length>0) {
for (jtr=0; jtr<1000; jtr++)
comboBox+="<option>"+textNodes[jtr].childNodes[0].nodeValue+"</option>";
} else {
break;
}
} catch(error) {
response=null;
textNodes=null;
break;
} finally {
response=null;
textNodes=null;
}
} //end of for loop
comboBox+="</select>";
/************************************/

/************************************/
//function to get the dolumn values from the view
function getViewColumn(serverName,databasePath,viewName,startIndex) {
var xmlHttp=xmlHttpRequest();
var url;

if (startIndex==-1) {
url="http://"+serverName+"/"+databasePath.replace(/\\/gi,"/")+"/"+viewName+"?readviewentries&count=-1";
} else {
if (typeof startIndex!='number') startIndex=0;
if (startIndex==0) {
url="http://"+serverName+"/"+databasePath.replace(/\\/gi,"/")+"/"+viewName+"?readviewentries&count=-1";
} else {
url="http://"+serverName+"/"+databasePath.replace(/\\/gi,"/")+"/"+viewName+"?readviewentries&start="+startIndex+"&count=-1";
}
}
var retValue
try {
var xmlHttp=xmlHttpRequest();
xmlHttp.open("GET",url,false);
xmlHttp.send(null);
retValue=xmlHttp.responseXML;
xmlHttp=null;
} catch (error) {
error.message += " <br>- in function getViewColumn(serverName,databasePath,viewName)<br>";
throw error;
}
return retValue;
}
/************************************/

/************************************/
//function to create sml http request
function xmlHttpRequest() {
var xmlhttp;
if (window.XMLHttpRequest)
{
// code for IE7+, Firefox, Chrome, Opera, Safari
xmlhttp=new XMLHttpRequest();
}
else if (window.ActiveXObject)
{
// code for IE6, IE5
xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
}
else
{
throw new Error("Your browser does not support XMLHTTP! <br> - in function xmlHttpRequest()");
return;
}
return xmlhttp;
}
/************************************/

Wednesday, December 23, 2009

Javascript API - Creating draggable components

This API helps you perform Simple Drag and Drop using plain javascript.

The implementation of this API is quite easy though.

1. Make a js file containing the following javascript class
2. Include that to your HTML Page
3. In the onload event of the page put the following code,

var dragObj=new DraggableObject();
dragObj.makeDraggable(parameter);


You can also put the above mentioned code in some button and perform the test.

parameter - id of the element you want to make draggable
- or name of the element you want to make draggable
- or the object reference of the element you want to make draggable


/****************************************************************************
@Type : Class/Function
@Name : DraggableObject
@getter : getDebugFlag - To get the debugFlag value
@Setter : setDebugFlag(isDebugFlag)- To set the debugFlag value
@Property : makeDraggable(objectItem) - Property to make an object draggable
@Property : getMouseCoords(event) - To get the coordinates of mouse on screen
@Created : 23rd, Dec'09
@Author : Karthikeyan A
*****************************************************************************/

function DraggableObject() {
//private variables
var dragObject=null;
var mouseOffset = null;
var classRef=this;
var debugFlag=false;
init();

//defining getters
this.getDebugFlag=function() {
return debugFlag;
}

//defining setters
this.setDebugFlag=function(isDebugFlag) {
debugFlag=isDebugFlag;
}

//defining properties
this.makeDraggable = function(objectItem){
//if the objectItem subjected for drag is not an object,
//then check whether it is a name or an id and get the
// associated object. If every thing fails return an error
if (typeof objectItem!='object') {
objectItem=document.getElementById(objectItem)
if (typeof objectItem!='object') {
eval('item=document.forms[0].'+objectItem);
if (typeof objectItem!='object') {
throw new Error(objectItem + 'does not refer to a valid HTML Element');
return;
}
}
}

objectItem.onmousedown = function(){
dragObject = this;
}
}

this.getMouseCoords=function(m){
if (document.all) { //code fragment to go work in IE
var tmpX = event.clientX;
var tmpY = event.clientY;
} else { //code fragment to work in firefox, opera and safari
var tmpX = m.pageX;
var tmpY = m.pageY;
}

if (!document.body.scrollTop) { //code fragment to go work in IE
var iL = document.documentElement.scrollLeft;
var iV = document.documentElement.scrollTop;
} else { //code fragment to work in firefox, opera and safari
var iL = document.body.scrollLeft;
var iV = document.body.scrollTop;
}
return {x: tmpX + iL, y: tmpY + iV};
}

// methods for internal usage
function init(){
document.onmousemove = mousemove;
document.onmouseup = mouseUp;
}

function mouseUp(ev){
dragObject = null;
}

function mousemove(event) {
if (dragObject==null) return;
var xy=classRef.getMouseCoords(event);
dragObject.style.position = 'absolute';
dragObject.style.top = xy.y;
dragObject.style.left = xy.x;
}
}

Change Style Sheets Using Javascript

Themes are one of the most beautiful features that gives a web application very impressive survey and feed back. I happened to win a Client project because of the same.

It is quite easy to obtain this feature as well. Following is a simple illustration to perform the same.

Let me assume that my html page contains one link tag, pointing to a specific css file as follows,

<html>
 <head>
  <link rel='stylesheet' type='text/css' href='style1.css'/>
   ....
 </head>
 <body>
  ....
  <input type='button' value='Change Style' onclick='changeStyle()'/>
  ....
 </body>
<html>

My changeStyle() function shall contain the following code to help apply a new style sheet for my page with out refreshing the same,

function changeStyle() {
  document.getElementsByTagName('link')[0].href='style2.css';
}

Also you can enable and disable the style sheets that you have already defined in the following way

document.styleSheets[StyleSheetIterator].disabled = false;
document.styleSheets[StyleSheetIterator].disabled = true;

This seems to work fine with the following browsers,
Internet Explorer 6+,
Mozilla Firefox 3.5.6,
Opera 10.10
Safari 4.0.4


This is a very simple example and I have aimed at helping you understand the very core of the issue. In real time there would be more than one css applied for the page and you may have to develop your own mechanism of tracking and changing the ones as necessary.

Creating and Consuming Web Service providers in Lotus Notes



Creating WebService
The following is a simple practical example, illustating how to create a simple web service and use the same.

Technologies Used: Lotus Script, Web Service Providers-Lotus Notes 8.5

1. Create a new WebServiceProvider design element

2. Give it a name : SimpleWebServiceProvider

3. Put the following into the Options section

Option Public
Option Declare
%INCLUDE "lsxsd.lss"

4. In the Declarations section add the following code

Class SimpleWebServiceProvider

Function echo(test As String)
echo= test
End Function

End Class

5.Ensure that the properties section of the web service is similar to the following

6. Save and close the web service

To ensure your webservice is working fine, open your browser and type in the following url as applicable to you,
"http: //servername/databasepath/SimpleWebServiceProvider?wsdl"

If you get a page with wsdl loaded then your webservice is working fine.

If your application is in your local hard disk and not in a server, then you may have to do the following work around to start the nhttp task for your local notes client.
1. Add a name, named "Anonymous" to the ACL and specify the same as person and manager
2. Open up a form in the Database and preview the same in IE and ensure that the form gets opened in the browser.

Once the above step is successful, type in the webservice url and preview it. Now it should run.


Consuming WebService

1. Open a database
2. Add a new form to it or open up an existing form
3. Add a new button to it
4. Put the following code into that button

Dim Client As Variant 'declares a variant
Set Client = CreateObject("MSSOAP.SoapClient") ' creats a soap client object
Call Client.MSSoapInit("http: //servername/databasepath/SimpleWebServiceProvider?wsdl")
Msgbox Client.echo("Yeppiee my webservice worked") ' calls a function defined in the web service


5. Preview the form in Notes Client
6. Click the button and view the Results :)

$KeepPrivate - Restricting users from copying and pasting your documents

$KeepPrivate is a reserved field that is not documented in the designer help.

If a document that you see in a view contains this particular item with a value of 1 in it then you will not be able to copy paste this document even though you have manager access and no prevention for pasting has been done from the view level.

There are occasions when you are unable to forward, print or copy a message or form despite the fact that you have not chosen this option. How does this occur, and what are all the ways of adding the $KeepPrivate field to a document in order to discourage copying, printing and forwarding?

Solution:
When the $KeepPrivate field is present in a document with a value of "1", you will be unable to forward, print or copy the document. There are at least 4 ways in which the $KeepPrivate field (with a value of "1") can be added to a document. Please be advised that however you add this field, it is only a deterrent and not a true security feature. There are other ways people can still copy, forward or print the document if they are determined to do so.
Of the following options, only option 3 requires direct interaction from the end user of the form. Options 1, 2 and 4 do not require any interaction from the end user of the form, and may be the reason this field is added without the user/creator of the document's knowledge.

Option #1: Through Access Control List (ACL) Settings
If the creator of a document has author access or above in the ACL of a particular database, however the 'Replicate or copy documents' permission is not selected for that user, then whenever that user creates a document, the $KeepPrivate field will be created and its value will be set to "1". For more information on this option, please consult the Domino Administrator's Guide.

Option #2: Through the Design Client
Assuming you have the appropriate access in the ACL for the database, you can access and change the form properties in the Design client. On the Security Tab of the Form Properties, you can select 'Disable printing/forwarding/copying to clipboard'. Any document based on this form will have the $KeepPrivate field with its value set to "1". End users of the form may not realize this option is in place when they create documents using this form. For more information on this option, please consult the Domino Designer's Guide.
NOTE: Notes 3.x clients do not recognize the "Prevent copying" functionality. Therefore, Notes 3.x users are able to print, forward, and copy memos even when the "Prevent copying" option is enabled.

Option #3: As a 'Delivery Option' when sending mail
When you create a new message, you can select Actions, Delivery Options, and on the 'Basics' tab, you can select the 'Prevent copying' option and that will add the $KeepPrivate field (with a value of "1") to the sent message so that the receiver of the message cannot copy, print or forward the message with history. For more information on this option, please consult the Notes Online Help

Option #: 4: Manually adding the field to the form
You can manually add this field to a form if you have Design access to the form in question by either directly adding the field to the form, and setting the default value for the field to "1", or you can write an agent that will add the field, and set its value to any or all existing documents in the database. For more information on this option, please consult the Domino Designer's Guide.
 

Replacing restricted strings in file paths

There has been a lot of occasions on which I have to provide users an option to specify a file path to export certain criteria from my application.

Though they would work fine, the export would fail abruptly if user enters a few strings like *, ? etc in the file path and that will lead to a lot of nuisance and noise X-)

The following fragment of code shall take care of those issues if implemented

'**********************************************************************************'@Author : Karthikeyan A
'@Purpose : replace the strings /\|?><*:" from any strings concerned with naming OS files especially '@Type : Function '@Name : replaceRestrictedStrings '@Param : text As String, customValue As String '@Return : String '**********************************************************************************

Function replaceRestrictedStrings(text As String, customValue As String) As String
Dim restrictedStrings(0To 8) As String
restrictedStrings(0)={\}
restrictedStrings(1)={/}
restrictedStrings(2)={:}
restrictedStrings(3)={*}
restrictedStrings(4)={?}
restrictedStrings(5)={"}
restrictedStrings(6)={<} restrictedStrings(7)={>}
restrictedStrings(8)={|}

'find the occurences of the above set of strings and replace them by customValue provided by the user
Forall vall In restrictedStrings
While Instr(text,vall)
text=Replace(text,vall,customValue)
Wend
End Forall

'calculate the return value of the function
replaceRestrictedStrings=text
End Function

Checking For User's Role

'**********************************************************************************'@Author : Karthikeyan A
'@Purpose : To check whether a user has a specified role in a database or not
'@Type : Function
'@Name : isRoleEnabled
'@Param : userName As String,db As NotesDatabase,roleName As String
'@Return : Boolean ( True if user has a specified role in the database provided, false otherwise)
'@Status : Working
'@UpdatedHistory
'@Updatedby :
'**********************************************************************************

Function isRoleEnabled(userName As String,db As NotesDatabase,roleName As String) As Boolean

'mark theflow of control getting inside the current function
isRoleEnabled =False

'handle errors in case of abrupt termination
On Error Goto errHandler

Dim userRoles As Variant
Dim acl As notesacl
Dim aclEntry As NotesACLEntry

'set the handle to access the access control list of the provided database
Set acl=db.ACL

'get the handle to fetch the details relevant to user name provided in the parameter list
Set aclEntry=acl.GetEntry(userName)

userRoles=aclEntry.Roles
Forall roles In userRoles
'mark the flow of control getting out of the current function
If roles=roleName Then isRoleEnabled=True
End Forall

Exit Function
errHandler:
Exit Function
End Function

Removing check on "Prohibit design refresh or replace to modify"

This fragment of code shall help you to remove the mark against "Prohibit design refresh or replace to modify" in the design document properties of the design elements in the database against which the agent is run

'**********************************************************************************'@Author : Karthikeyan A
'@Purpose : To remove the mark against "Prohibit design refresh or replace to modify" in the design document
' properties of the design elements in the database against which the agent is run
'@Note : Can be used as a agent
'@Type : Sub
'@Name : agProhibitFlagRemover
'@Param : Nothing
'@Return : Nothing
'**********************************************************************************
Sub ProhibitFlagRemover

'initiation
'handle errors in case of abrupt termination
On Error Goto errHandler

'declare all necessary variables and objects required for further manipulation
Dim session As New NotesSession
Dim workspace As New NotesUIWorkspace
Dim Server_Name As NotesName
Dim noteCol As NotesNoteCollection
Dim targetDB As NotesDatabase
Dim designDoc As notesdocument
Dim mailDoc As NotesDocument

Dim dbfile As Variant
Dim mailFile As Variant

Dim sendTo As String
Dim noteID As String
Dim flagValue As String
Dim updatedList As String
Dim serverName As String

Dim count As Integer
Dim accessLevel As Integer
Dim confirmation As Integer
Dim itr As Integer

'open the open database dialog box
dbFile= workspace.Prompt(13,"Choose database","Please select a database to process")

'if the user presses escape or the cancel button then skip the process
If Isempty(dbFile) Then
Exit Sub
End If

'set the handle for the target database from the users' selection
Set targetDB=session.GetDatabase(dbFile(0),dbFile(1),False)

'if the handle for the target database is not set then prompt the user and exit
If Not targetDB.IsOpen Then
Msgbox "The database that you have selected is not a valid file",,"Invalid file "
Exit Sub
End If

'check the current user's access level for the selected database
accessLevel=targetDB.QueryAccess(session.UserName)

'if the user does not have designer or higher access then inform the user and exit
If accessLevel < ACLLEVEL_DESIGNER Then Msgbox "You do not have sufficient access to perform this operation in the chosen database",,"Access denied" Exit Sub End If 'inform the user that proceeding further will sign all the design documents in the database and ask for confirmation 'if he/she wants to proceed else ask him to abort if he wants to use a different id confirmation= workspace.Prompt(PROMPT_YESNO,"Alert","Proceeding further will sign all the design elements in the database by your ID.If you are sure please click ""Yes"" to proceed." _ & Chr(13) & "Or if you want to switch to a different id before proceeding please click ""No""") 'if the user does not confirm then exit process else proceed If confirmation=0 Then Exit Sub End If 'get the collection of all design documents in the database Set noteCol=targetDB.CreateNoteCollection(True) 'build the note collection of the design documents Call noteCol.BuildCollection 'loop through the design documents and remove the "Prohibit Design Refresh" flag if enabled and update the status bar 'get the note id of the first design document in the database noteID=noteCol.GetFirstNoteId 'initializing the list of design documents that are updated as null updatedList="" itr=1 For count=1 To noteCol.Count 'set the handle handle for the design document using the note id found from the collection Set designDoc=targetDB.GetDocumentByID(noteID) 'look into the $Flag item in the design document to see if "Prohibit design refresh or replace to modify" is marked or not flagValue=designDoc.GetItemValue("$Flags")(0) 'if yes then remove it and save the design document If Instr(flagValue,"P") Then 'remove the prohibition mark flagValue=Replace(flagValue,"P","") 'update the design document Call designDoc.ReplaceItemValue("$Flags",flagValue) 'save the design document Call designDoc.Save(True,False) 'update the updated list of documents updatedList=updatedList & Cstr(itr) & ". " & designDoc.GetItemValue("$Title")(0) & Chr(13) itr=itr+1 'update the status bar Print designDoc.GetItemValue("$Title")(0) & " has been updated by removing the ""Prohibit design refresh or replace to modify"" flag" End If 'get the noteID of the next design document in the note collection noteID=noteCol.GetNextNoteId(noteID) Next 'if there are no design documents updated then skip the process else proceed If updatedList<>"" Then

'if the chosen database is in local then mark the servername as local else use the server name in which the database exists
If dbFile(0)="" Then
serverName="Local"
Else
serverName=dbFile(0)
End If

'detail the message that is to be mailed to the user
updatedList= "The database """ & dbFile(1) & """ in server """ & serverName & """ with title """ & dbFile(2) & """" _
& Chr(13) & Chr(13) & " has been processed and the ""Prohibit design refresh or replace to modify"" property has been unchecked " _
& Chr(13) & Chr(13) & updatedList

'get the name of the server in which the current database resides
Set Server_Name=session.CreateName(session.CurrentDatabase.Server)


'get the mail file name of the current user
mailFile=Evaluate({@NameLookup([NoUpdate];@Name([CN];"}+session.CommonUserName+{");"MailFile")})

'see if the current user has a mail file associated with his ID. if yes then send the mail to him else
' allow him to chose a mail address to send the mail to
If mailFile(0)<>"" Then
sendTo=session.UserName
Else
sendTo=workspace.PickListStrings(PICKLIST_NAMES,False)
'if the user does not pick any name to send mail to, then inform the user about the status and exit
If sendTo="" Then
Msgbox "The process has been completed successfully and you can find the list of design elements that were updated in the status bar" ,,"Successfully updated the database"
Exit Sub
End If
End If

'prepare a mail document and send the mail to the current user
Set MailDoc=session.CurrentDatabase.CreateDocument
mailDoc.Form="Memo"
mailDoc.sendTo=sendTo
mailDoc.Subject=dbFile(1) & " database in " & serverName & " has been updated"
mailDoc.Body=updatedList
Call mailDoc.Send(False)
'prompt the user that the information has been mailed to him and exit
Msgbox "The process has been completed successfully and the list of changes have been mailed to "& sendTo ,,"Successfully updated the database"
Exit Sub
End If

'prompt the user that no design document has been updated and exit
Msgbox "The process has been completed successfully and no change was made to the database",,"No changes were required"

Exit Sub
'prompt the errors that resulted in abrupt termination
errHandler:
Msgbox "Error: ***" & Error & "*** encountered on line *** " & Cstr(Erl) & " *** with error number *** " & Cstr(Erl) & " ***"
Exit Sub
End Sub

Processing Strings - Miscellaneous

The Code fragment below helps you to compare the contents of two different strings and return only those contents that are present in the target string but not in the source string.

This is a very special case though.


'**********************************************************************************
'@Author : Karthikeyan A
'@Purpose : To compare the contents of two different strings and return only those contents that are present in the target string but not in the source string
'@Type : Function
'@Name : filterDuplicates
'@Param : sourceValue As String,targetValue As String
'@Return : Boolean (has operation succeded, True stands for success and False stands for failure)
'**********************************************************************************
Function filterDuplicates(sourceValue As String,targetValue As String) As Boolean

Dim tempTarget As String
Dim splitSourceValue As Variant
Dim splitTargetValue As Variant

'mark the flow of contol, entering into the current function
filterDuplicates=False

' if the sourceValue is empty then exit the function
If Not sourceValue="" Then

'declare the variables necessary for further manipulation
tempTarget=""

'split the contents of the source string and see if they exist in the target string
'if yes then replace the corresponding value in the target string with null
splitSourceValue=Split(sourceValue,",")
Forall sourceVal In splitSourceValue

If Instr(targetValue,sourceVal) Then
targetValue=Replace(targetValue,sourceVal,"")
End If
End Forall

'skip the null values in the target string which resulted in the above step and assign it back to the target string
splitTargetValue=Split(targetValue,",")
Forall targetVal In splitTargetValue
If Not Trim(targetVal)="" Then
tempTarget=tempTarget+targetVal+","
End If
End Forall
tempTarget=Strleftback(tempTarget,",")

targetValue=tempTarget
End If

'mark the flow of control,moving out of the current function
filterDuplicates=True

End Function

Basic validation for Rich Text Fields

When a trim is enough for checking whether a field contains a value or not, It is not that simple with Rich Text fields.

The following function shall help you with that to some extent.

Limitiations
1. By default, The handle for the rich text field can be obtained only when a document is saved

2. Any Images copied and pasted into the rich text field from the clipboard cannot be processed

I understand the above two points as a limitation with Lotus Notes. Please correct me if I am wrong

3. In addition to this function, you may have to write some code to check whether the rich text field contains text in it. Though that can be implemented along with this function, It has not been done at this point of time


'**********************************************************************************
'@Author : Karthikeyan A
'@Purpose : To inform the user if a rich text field contains embedded objects or not.
'@Note : This is for front end usage only and cannot be used with scheduled agents
'@Note : Images pasted from clipboard will not be processed. I understand it as a limitation of IBM Lotus Notes. Please correct me if I am wrong
'@Type : Function
'@Name : validateRichTextField
'@Param : richTextFieldName As String
'@Return: Boolean (has operation succeded, True stands for success and False stands for failure)
'**********************************************************************************

Function validateRichTextField(richTextFieldName As String) As Boolean

'mark the flow of control getting inside the current function
validateRichTextField=False

'declare all the necessary objects and variables required for further manipulation
Dim workSpace As New NotesUIWorkspace
Dim uidoc As notesuidocument

Dim rtitem As NotesRichTextItem
Dim currDoc As notesdocument
Dim rtnav As NotesRichTextNavigator

Dim noOfEmbeddedItems As Integer

'set the handle for the current document
Set uidoc = workSpace.CurrentDocument

'save the current document
Call uidoc.Save

'set the back end handle for the current document
Set currDoc =uidoc.Document

'set the handle for the rich text field with the name provided as the parameter
Set rtitem=currDoc.GetFirstItem(richTextFieldName)
'if the handle for the rich text field is not set then exit function
If rtitem Is Nothing Then
Exit Function
End If

'initialise the no of items found to 0
noOfEmbeddedItems=0

'set the handle for the navigator to navigate inside the rich text field and find the number of embedded items
Set rtnav = rtitem.CreateNavigator

'for all embedded items found in the field increase the count of the number of
' items found in the field except for the text portion
If rtnav.FindFirstElement(RTELEM_TYPE_DOCLINK ) Or _
rtnav.FindFirstElement(RTELEM_TYPE_FILEATTACHMENT ) Or _
rtnav.FindFirstElement(RTELEM_TYPE_OLE) Or _
rtnav.FindFirstElement(RTELEM_TYPE_SECTION) Or _
rtnav.FindFirstElement(RTELEM_TYPE_TABLE) Or _
rtnav.FindFirstElement(RTELEM_TYPE_TABLECELL) Then
noOfEmbeddedItems=noOfEmbeddedItems+1
End If

'if the number of items found in the field is zero then mark the flow of control moving out of the
'current function as false (marking failure) else true (marking success)
If noOfEmbeddedItems=0 Then
Exit Function
End If

'mark the flow of control moving out of the current function
validateRichTextField=True

End Function

Sending Rich Text Notifications

'********************************************************************
'@Author : Karthikeyan A
'@Purpose : send rich text notification
'@Type : Function
'@Name : SendRichTextNotification
'@Return : Boolean (has operation succeded, True stands for success and False stands for failure)
'********************************************************************

Function SendRichTextNotification(recips As Variant, Subject As String, rtBody As NotesRichtextItem) As Boolean

'mark the flow of control getting inside the current function
SendRichTextNotification=False

'declare objects and variables required for further manipulation
Dim session As NotesSession
Dim doc As NotesDocument
Dim mailbody As NotesRichTextItem


'set the handle for the current session
Set session=New NotesSession

'create a mail document which is to be mailed
Set doc = session.CurrentDatabase.CreateDocument

'define its attributes
doc.Form = "Memo"
doc.Subject = Subject
doc.SendTo = recips

'define its body
Set mailBody = New NotesRichTextItem(doc, "Body")
Call mailBody.AppendRTItem(rtBody)

'send the mail
Call doc.Send(False)

'mark the flow of control moving out of the current function
SendRichTextNotification=True

End Function

Sending plain text notifications

'**********************************************************************************'@Author : Karthikeyan A
'@Purpose : send mails with plain text as body content
'@Type : Function
'@Name : sendPlainTextNotification
'@Param : sendTo As Variant,subject As String,body As String
'@Return : Boolean (has operation succeded, True stands for success and False stands for failure)
'**********************************************************************************
Function sendPlainTextNotification(sendTo As Variant,subject As String,body As String) As Boolean

'mark the flow of control getting inside the current function
sendPlainTextNotification=False

'declare all the objects and variables required for further manipulation
Dim session As NotesSession
Dim mailDoc As NotesDocument

'set the handle for the current database
Set session=New NotesSession

'create the mail document that is to be mailed
Set MailDoc=session.CurrentDatabase.CreateDocument

'define the attributres for the mail document
mailDoc.Form="Memo"
mailDoc.sendTo=sendTo
mailDoc.Subject=subject
mailDoc.Body=body

'mail the document
Call mailDoc.Send(False)

'mark the flow of control moving out of the curren function
sendPlainTextNotification=True

End Function

Delete all profile documents in a database

'**********************************************************************************'@Author : Karthikeyan A
'@Purpose : delete all profile documents in the mentioned database
'@Type : Function
'@Name : deleteAllProfileDocsInGivenDatabase
'@Param : db As NotesDatabase
'@Return : Boolean (True indicates success and False otherwise)
'**********************************************************************************

Function deleteAllProfileDocsInGivenDatabase(db As NotesDatabase) As Boolean
'mark the flow of control getting inside the current function
deleteAllProfileDocsInGivenDatabase=False

Dim profileDocCollection As NotesDocumentCollection

'if the provided database does not exist then return false and exit
If Not db.isOpen then
set deleteAllProfileDocsInGivenDatabase=false
Exit function
End If

'set the handle for the set the profile documents in the database
Set profileDocCollection=db.GetProfileDocCollection

'delete all the profile documents
Call profileDocCollection.RemoveAll(True)

'mark the flow of control getting out of the current function
deleteAllProfileDocsInGivenDatabase=True
End Function

Get profile documents from a given database

'**********************************************************************************
'@Author : Karthikeyan A
'@Purpose : get profile documents from a specific db
'@Type : Function
'@Name : getProfileDocs
'@Param : db As NotesDatabase
'@Return : NotesDocumentCollection(profile doc collection in a db)
' and Nothing is db doesnot exist or no profile doc is found
'**********************************************************************************

Function getProfileDocs(db As NotesDatabase) As notesdocumentcollection

If not db.isOpen then
Set getProfileDocs=nothing
Exit Function
End If

'set the handle for the set the profile documents in the database and return the same
Set getProfileDocs=db.GetProfileDocCollection

End Function

Tuesday, December 22, 2009

Check Access To Lotus Notes Database

'**********************************************************************************
'@Author : Karthikeyan A
'@Purpose : check access level to a database and return false if not designer or higher
'@Type : Function
'@Name : checkAccessToDatabase
'@Param : targetDB As NotesDatabase
'@Return : Boolean
'**********************************************************************************

Function checkAccessToDatabase (targetDB As NotesDatabase) As Boolean

'mark the flow of control getting inside the current function
checkAccessToDatabase=False

'declare the necessary variables and functions
Dim session As New NotesSession
Dim accessLevel As Integer

'if the handle for the target database is not
'set then prompt the user and exit
If Not targetDB.IsOpen Then
Msgbox "The database that you have selected is not a valid file" _
,,"Invalid file "
Exit Function
End If

'check the current user's access level for the selected database
accessLevel=targetDB.QueryAccess(session.UserName)

'if the user does not have designer or higher
'access then inform the user and exit
If accessLevel < ACLLEVEL_DESIGNER Then
Msgbox "You do not have sufficient access to perform this " & _
"operation in the chosen database",,"Access denied"
Exit Function
End If

'mark the flow of control getting inside the current function
checkAccessToDatabase=True
End Function

%TEMP% - Temporary Folder path in Lotussscript

'**********************************************************************************
'@Author : Karthikeyan A
'@Purpose : To get the folder path of the windows temp folder configured in Notes.ini
'@Type : Function
'@Name : getTempFolderPath
'@Return : String (path of the temp folder)
'************************************************************************************
Function getTempFolderPath() As String

'declare the session object to refer the current user's session
Dim session As New notessession

'variable to hold the path of the temp folder
Dim tempDir As String

'get the mentioned environment string value from the notes.ini file
tempDir = session.GetEnvironmentString("SU_FILE_CLEANUP",True)

'the above value shall return a value like "C:\DOCUME~1\....\Temp\smkits"
'skip the contents after the word Temp in the string
tempDir=Strleftback(tempDir,"\")

'return the resultant as the temp folder's path
getTempFolderPath=tempDir
End Function

AJAX API

function AjaxProcess() { //start of class AjaxProcess


/*

* @Type : Class/Function

* @Name : AjaxProcess

* @Param : Mandatory -url - non empty url string

* @Param : Optional - typeOfOperation - boolean, true for synchronous process,and false otherwise - Defaults to false

* @Param : Optional - params - string, parameters to be sent via post request. Presence of this param will force a post request

* @Setter : setSource(newSourceURL)- To set the source for the ajax request

* @Setter : setAsynchronous(typeOfOperation)- To set the type of ajax request

* @Setter : setXMLResponse(xmlResponseFlag)- required to obtain xml response

* @Setter : setDebugFlag(debugFlag)- To help developers to check with

custom alerts with out affecting the flow

* @Property : initiate() - To initate the ajax request using an object of this class

* @Property : getResponse() - To get the response from the server using an object of this class

* @Property : getResponseText()- To get the text response from the server using an object of this class

* @Property : getHtmlFilteredResponseText - To get the html filtered text response from the server using an object of this class

* @Property : getResponseTable()- To get the table formated response from the server using an object of this class (special case will be rarely used when the server prints a html table as a response)

* @Property : rebuildRequest() - To re-initate the ajax request using an object of this class

* @Property : loadXMLString(textXml) - To load the given the xml string and convert the same into a xml document by parsing the same

* @Created : 13th, Oct'09

* @Author : Karthikeyan A

**/



var urlToProcess= arguments[0];

var opType=arguments[1];

var params=arguments[2];



var xmlHttpReq=null;

var ajaxResponse=null;



var isXMLResponse=false;

var isDebug=false;



/************Getters and Setters************************/

this.setSource= function(newSourceURL) {

if (typeof newSourceURL!="string") {

if (isDebug) alert("The param newSourceURL is not a string in function setSource(newSourceURL)");

newSourceURL=""

}

urlToProcess=newSourceURL;

}



this.setAsynchronous=function(typeOfOperation) {

if (typeof typeOfOperation!="boolean") {

if (isDebug) alert("The param typeOfOperation is not a boolean in function setAsynchronous(typeOfOperation)");

typeOfOperation=false;

}

opType=typeOfOperation;

}



this.setXMLResponse=function(xmlResponseFlag) {

if (typeof xmlResponseFlag!="boolean") {

if (isDebug) alert("The param typeOfOperation is not a boolean in function setXMLResponse(xmlResponseFlag)");

xmlResponseFlag=false;

}

isXMLResponse=xmlResponseFlag;

}



this.setDebugFlag=function(debugFlag) {

if (typeof xmlResponseFlag!="boolean") {

alert("The param typeOfOperation is not a boolean in function setDebugFlag(debugFlag)");

xmlResponseFlag=false;

}

isDebug=debugFlag;

}



/********* properties to be used by users***************/

//function to initate the ajax request using an object of this class

this.initiate = function() {

try {

//check for validity and re-initialize (if necessary) the parameters required for the class

if (typeof urlToProcess!="string") {

if (isDebug) alert("The param urlToProcess is not a string in function AjaxProcess(url,typeOfOperation)");

throw new SyntaxError("The param urlToProcess is not a string in function AjaxProcess(url,typeOfOperation)");

} else if (this.trim(urlToProcess)=="") {

if (isDebug) alert("The param urlToProcess is cannot be an empty string in function AjaxProcess(url,typeOfOperation)");

throw new SyntaxError("The param urlToProcess cannot be an empty string in function AjaxProcess(url,typeOfOperation)");

}



if (typeof typeOfOperation!="boolean") {

if (isDebug) alert("The param typeOfOperation is not a boolean in function AjaxProcess(url,typeOfOperation)");

typeOfOperation=false;

}



//create an xml http request

xmlHttpReq=this.xmlHttpRequest();



//if parameters- the thrid argument has been provided, force a post request

if (typeof params=="string") {

//mark the source and async type of the request

xmlHttpReq.open("POST",urlToProcess,opType);

//send request to server

xmlHttpReq.send(params);

} else {

//mark the source and async type of the request

xmlHttpReq.open("GET",urlToProcess,opType);

//send request to server

xmlHttpReq.send(null);

}



//get the response in text/xml format as desired (governed by the usXMLResponse flag)

if (!isXMLResponse) {

ajaxResponse=xmlHttpReq.responseText;

} else {

ajaxResponse=xmlHttpReq.responseXML;

}

} catch(error) {

//throw formated error messages in case of errors

error.message="API Error in method AjaxProcess(url,typeOfOperation).initiate(): -"+error.message;

throw error

}

}



this.getResponseXML=function() {

return xmlHttpReq.responseXML;

}



this.getResponseText=function() {

return xmlHttpReq.responseText;

}



if (!isXMLResponse) { //start of isXMLResponse if

this.getHtmlFilteredResponseText=function() {

var htmlFilteredajaxResponse=ajaxResponse.replace(/(<([^>]+)>)/ig,"");

htmlFilteredajaxResponse=this.trim(htmlFilteredajaxResponse, "\n");

return htmlFilteredajaxResponse;

}



this.getResponseTable=function() {

//To get the table formated response from the server using an object of this class

//(special case will be rarely used when the server prints a html table as a response)

var ajaxTableResponse=this.trim(ajaxResponse, "\n")

return ajaxTableResponse;

}

} //end of isXMLResponse if



this.rebuildRequest=function() {

xmlHttpReq=null;

ajaxResponse=null;

this.initiate();

}



this.recycle=function() {

xmlHttpReq=null;

ajaxResponse=null;

urlToProcess= null;

opType=null;

isXMLResponse=null;

}



/*****************Back end usage only****************************/

//function to generate a xmlhttprequest

this.xmlHttpRequest= function() {

var xmlhttp;

try {

if (window.XMLHttpRequest) {

// code for IE7+, Firefox, Chrome, Opera, Safari

xmlhttp=new XMLHttpRequest();

} else if (window.ActiveXObject) {

// code for IE6, IE5

xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");

} else {

throw new Error("Your browser does not support XMLHTTP! - in function xmlHttpRequest()");

return;

}

} catch(error) {

error.message="API Error in method AjaxProcess(url,typeOfOperation).xmlHttpRequest(): -"+error.message;

xmlhttp=null;

throw error

} finally {

return xmlhttp;

}

}



this.loadXMLString= function(textXml) {

var xmlDoc=null;

try {

if (window.DOMParser) { //firefox, Chrome, Opera, Safari

parser=new DOMParser();

xmlDoc=parser.parseFromString(textXml,"text/xml");

} else { // Internet Explorer

xmlDoc=new ActiveXObject("Microsoft.XMLDOM");

xmlDoc.async="false";

xmlDoc.loadXML(textXml);

}

} catch(error) {

//throw formated error messages in case of errors

error.message="API Error in method AjaxProcess(url,typeOfOperation).loadXMLString(textXml): -"+error.message;

//mark the return value

xmlDoc=null;

throw error;

} finally {

return xmlDoc;

}

}



//function to trim of leading and trailing spaces

this.trim=function(str, chars) {

return this.ltrim(this.rtrim(str, chars), chars);

}



//function to trim of white spaces on the left

this.ltrim=function(str, chars) {

chars = chars

"\\s";

return str.replace(new RegExp("^[" + chars + "]+", "g"), "");

}



//function to trim of white spaces on the right

this.rtrim=function(str, chars) {

chars = chars

"\\s";

return str.replace(new RegExp("[" + chars + "]+$", "g"), "");

}

} //end of class AjaxProcess

Open Database Dialog - Lotus Script


There was one project in which I had to provide an option for the user to choose a database from a particular server in Lotus Notes.

Since I am a dummy, I started with the scalar thinking of how I should develope a UI for the same, how to build the list of databases, where to look for the names of the databases etc....

It was then that I found a un-documented feature (I mean it is not their in the designer help) that does what I need in Lotusscript.

It is hidden inside one of the constructors of workspace.Prompt methods and is as follows....

empDBObj= workspace.Prompt(13,"","")

Update all documents in a View- Lotus Notes

Recently in one of my projects, I happened to add a computed field to an existing form using which lakhs of documents have been created already.

So I had to write a one time agent that shall update all these existing documents with the new created field.

Eventually I ended up writing an agent which had a code fragment similar to the one as follows,

....get first document...
call doc.computewithform(true,false)
call doc.save(true,false)
.... loop to the next document....


But this failed. I was not able to see the newly added field in the document properties, while opening the same document in edit mode and saving the same works. It got me puzzled and now I understand that it is a issue with Lotus Notes, something similar to the doc.AppendItemValue command.

Eventually I searched through the forums and got this brilliant suggestion from a person and it said,

use @Command([ToolsRefreshAllDocs]);@All

Also the post said that the computeWithForm is not so consistent.

It saved me a lot of time and It was very fast when compared to the lotusscript agent as well.

%INCLUDE Directive in Lotusscript

The purose of %Include directive is to inserts the contents of a text file into the module where the directive appears at compile time.

I have never used this in any of my projects, but still I feel that this could be a very useful tool when it comes to over coming certain size limitations of lotus notes.

Following are a few scenarios that I could think about,

(1) The 32/64k limit applies to all events within an object.
(2) An object is anything with its own events.
(3) Buttons, forms, fields, and agents are all separate objects and have their own 32/64k limits.

For example, if you have code in the QueryOpen, PostOpen and QuerySave events within one form, all these count toward the form's 32/64k limit because they are all events for the form object.

Following is a procedure that might help you to understand how you can use %INCLUDE directive to by pass the above mentioned limitations
1) Put the script in a text file and use the LotusScript %include command to load it. This script is not counted toward the memory limit. This is definitely the best workaround.
2) Put pieces of the script in entering events of hidden fields and use the LotusScript GotoField method to trigger the scripts.
3) Put additional scripts in subform events and embed the subforms in the form running into the memory limit. For example, if you're running out of space in the form object and wish to place more script in the PostOpen event, create a subform, place the desired code in its PostOpen event and insert the subform into the form.

The following is shall help you with a simple illustration, which will give you a more clear idea of %INCLUDE directive

1. Open a form in Lotus Notes Designer
2. Add a button to it
3. In the Options event add the following code
%INCLUDE "C:\customLss.dat"
4. In the onclick event add the following code
Call alert()
5. Open a notepad and put the following code in the same
Function alert()
Msgbox "this is from a text file called using %include directive"
End Function

6. Save the file as "C:\customLss.dat"
7. Preview the form in the Notes Client and click the button
8. You will get an alert displaying the following text "this is from a text file called using %include directive"
9. This indicates that the code fragment present in the text file, that you have put on the note pad had executed

Monday, December 21, 2009

Lotusscript HashMap

I often had issues when it came to tracking complex relations between strings in lotusscript.

For example, in a situation where, you got to keep adding email ids and names of people to multiple lists and to ensure that no person has multiple email ids recorded, is an cumbersome situation. It is possible, but involves lot of thinking

I felt a feature like HashMap in Java would be handy. Hence I got the Idea of developing the same in lotus script.

So here we go, the following is a lotus script hash map. Dont hate me if u have a better one...:).

Note : This class works only for string inputs...i.e, both the Key and Value must be a String

/*******************************************************/
Public Class HashMap

    'global variables
    Private hashMapList List As String
    Private mapSize As Long
    Private keyArray() As String
    Private valueArray() As String

    'property to add a new key value pair to the hashmap
    Function putValue(key As String,value As String)
        If Not Me.contains(key) Then
            ReDim Preserve keyArray(mapSize) As String
            ReDim Preserve valueArray(mapSize) As String
            keyArray(mapSize)=key
            valueArray(mapSize)=value
            mapSize=mapSize+1
        End If
        valueArray(ArrayGetIndex(keyArray,key))=value
        Me.hashMapList(key)=value
    End Function

    'property to get the value of the key in the hashmap
    Function getValue(key) As String
        On Error GoTo errHandler
        getValue = CStr(hashMapList(key))
        Exit Function
    errHandler:
        getValue= ""
        Exit Function
    End Function

    'property to check if the given key is already present in the hash map
    Function contains(key) As Boolean
        On Error GoTo errHandler
        If hashMapList(key)<>"" Then
            contains=True
        End If
        Exit Function
    errHandler:
        contains=False
        Exit Function
    End Function

    'property to return the size of the hashmap
    Function getSize As Long
        getSize=mapSize
    End Function

    'function to sort the contents of the Hash map in ascending order
    Function sort(ascending As Boolean)
        Dim sortItr As Integer
        Dim tempValue As String
        Dim tempKey As String
        Dim currMapSize As Integer
        Dim swapi As Integer,swapj As Integer
        Dim posItr As Integer
  
        On Error GoTo errHandler
        currMapSize=Me.getSize()-1
        For swapi=0 To currMapSize
            For swapj=swapi+1 To currMapSize
                If ascending Then
                    If CLng(valueArray(swapj))>CLng(valueArray(swapi)) Then
                        tempValue=valueArray(swapi)
                        valueArray(swapi)=valueArray(swapj)
                        valueArray(swapj)=tempValue
                        tempKey=keyArray(swapi)
                        keyArray(swapi)=keyArray(swapj)
                        keyArray(swapj)=tempKey
                    End If
                Else
                    If  CLng(valueArray(swapj)) < CLng(valueArray(swapi)) then
                        tempValue=valueArray(swapi)
                        valueArray(swapi)=valueArray(swapj)
                        valueArray(swapj)=tempValue
                        tempKey=keyArray(swapi)
                        keyArray(swapi)=keyArray(swapj)
                        keyArray(swapj)=tempKey
                    End If
                End If
            Next
        Next

        'clear the contents of the existing list
        Call Me.clear()
      
        'populate the list in the sorted order
        For posItr=0 To currMapSize
            hashMapList(keyArray(posItr))=valueArray(posItr)
        Next

        Exit Function
        'inform the user regarding the error that resulted in abrupt termination
    errHandler:
        MsgBox "An error ***" & Error & "*** occured on line ***" & CStr(Erl) & "*** with error number ***" _
        & CStr(Err) & "*** in function Sort in class HashMap",,"Error occured"
        Print "An error ***" & Error & "*** occured on line ***" & CStr(Erl) & "*** with error number ***" _
        & CStr(Err) & "*** in function Sort in class HashMap",,"Error occured"
        Exit Function
    End Function
  
    'destructor
    Function clear
        Erase hashMapList
    End Function

    Function getKeyArray()
        getKeyArray=keyArray
    End Function

    Function getValueArray()
        getValueArray=valueArray
    End Function
End Class
/********************************************************?

I have tested the above class using a button with the following code, seems to work for me
/***********************/
Dim hashMp As Variant
Set hashMp=New HashMap

Call hashMp.putValue("a","b")
Call hashMp.putValue("b","c")
Call hashMp.putValue("c","d")
Msgbox hashMp.getValue("a")    ' alerts b
Msgbox hashMp.getValue("b")    ' alerts c
Msgbox hashMp.getValue("c")    ' alerts d 
Msgbox hashMp.getSize()        ' alerts 3
Msgbox hashMp.contains("a"),,hashMp.contains("d")
' alerts true,,false  //(msgbox sense hi hi)

/***********************/

Best wishes,
Karthick