Code – Read from and Write to Windows Registry in Lotusscript

A question was posted in the IBM DeveloperWorks forum for Notes/Domino 8 about the possibility to detect from within Notes if a computer is equipped with a touch screen. The answer was that you have to check if a specific DLL is installed, which is done though the registry. The original posted then asked how to do that in Lotusscript, so I deceded to simply post some code I am using. I did not write this code, and I don't know who originally did. I think I may have taken some VB code and simply adapted it for Lotusscript. I plan to rewrite this a s a class when I have some time. In the mean time, here is the code.   Option Public Option Declare Dim REG_NONE As Long Dim REG_SZ As Long Dim REG_EXPAND_SZ As Long Dim REG_BINARY As Long Dim REG_DWORD As Long Dim REG_DWORD_LITTLE_ENDIAN As Long Dim REG_DWORD_BIG_ENDIAN As Long Dim REG_LINK As Long Dim REG_MULTI_SZ As Long Dim REG_RESOURCE_LIST As Long Dim REG_FULL_RESOURCE_DESCRIPTOR As Long Declare Function RegCloseKey Lib "advapi32.dll" (Byval hKey As Long) As Long Declare Function RegCreateKeyEx Lib "advapi32.dll" Alias "RegCreateKeyExA" (Byval hKey As Long, _ Byval lpSubKey As String, Byval Reserved As Long, Byval lpClass As String, _ Byval dwOptions As Long, Byval samDesired As Long, Byval lpSecurityAttributes As Long, _ phkResult As Long, lpdwDisposition As Long) As Long Declare Function RegOpenKeyEx Lib "advapi32.dll" Alias "RegOpenKeyExA" (Byval hKey As Long, _ Byval lpSubKey As String, Byval ulOptions As Long, Byval samDesired As Long, _ phkResult As Long) As Long Declare Function RegSetValueExString Lib "advapi32.dll" Alias "RegSetValueExA" (Byval hKey As Long, _ Byval lpValueName As String, Byval Reserved As Long, Byval dwType As Long, Byval lpValue As String, _ Byval cbData As Long) As Long Declare Function RegSetValueExLong Lib "advapi32.dll" Alias "RegSetValueExA" (Byval hKey As Long, _ Byval lpValueName As String, Byval Reserved As Long, Byval dwType As Long, lpValue As Long, _ Byval cbData As Long) As Long Declare Function RegQueryValueExString Lib "advapi32.dll" Alias "RegQueryValueExA" _ (Byval hKey As Long, Byval lpValueName As String, Byval lpReserved As Long, lpType As Long, _ Byval lpData As String, lpcbData As Long) As Long Declare Function RegQueryValueExLong Lib "advapi32.dll" Alias "RegQueryValueExA" _ (Byval hKey As Long, Byval lpValueName As String, Byval lpReserved As Long, lpType As Long, _ lpData As Long, lpcbData As Long) As Long Declare Function RegQueryValueExNULL Lib "advapi32.dll" Alias "RegQueryValueExA" _ (Byval hKey As Long, Byval lpValueName As String, Byval lpReserved As Long, lpType As Long, _ Byval lpData As Long, lpcbData As Long) As Long ' --- Registry key values Const HKEY_CLASSES_ROOT = &H80000000 Const HKEY_CURRENT_USER = &H80000001 Const HKEY_LOCAL_MACHINE = &H80000002 Const HKEY_USERS = &H80000003 Const HKEY_CURRENT_CONFIG = &H80000005 ' --- Registry return values Const ERROR_NONE = 0 Const ERROR_BADDB = 1 Const ERROR_BADKEY = 2 Const ERROR_CANTOPEN = 3 Const ERROR_CANTREAD = 4 Const ERROR_CANTWRITE = 5 Const ERROR_OUTOFMEMORY = 6 Const ERROR_INVALID_PARAMETER = 7 Const ERROR_ACCESS_DENIED = 8 Const ERROR_INVALID_PARAMETERS = 87 Const ERROR_NO_MORE_ITEMS = 259 ' --- Registry access key Const…

3 Comments

Free Code – Class to read URL name-value pairs

Here is another little code snippet I want to share. I use it all the time in my Lotusscript-based Domino web agents, and I figured that other could benefit from it as well. It is just an easy way to check for and read the name-value pairs (arguments) passed from the browser to the web server by HTTP GET or POST calls. Put the code below in a script library, I call it Class.URL: %REM Library Class.URL Created Oct 9, 2014 by Karl-Henry Martinsson Description: Lotusscript class to handle incoming URL (GET/POST). %END REM Option Public Option Declare %REM Class URLData Description: Class to handle URL data passed to web agent %END REM Class URLData p_urldata List As String %REM Sub New() Description: Create new instance of URL object from NotesDocument %END REM Public Sub New() Dim session As New NotesSession Dim webform As NotesDocument Dim tmp As String Dim tmparr As Variant Dim tmparg As Variant Dim i As Integer '*** Get document context (in-memory NotesDocument) Set webform = session.DocumentContext '*** Get HTTP GET argument(s) after ?OpenAgent tmp = FullTrim(StrRight(webform.GetItemValue("Query_String")(0),"&")) If tmp = "" Then '*** Get HTTP POST argument(s) after ?OpenAgent tmp = FullTrim(StrRight(webform.GetItemValue("Request_Content")(0),"&")) End If '*** Separate name-value pairs from each other into array tmparr = Split(tmp,"&") '*** Loop through array, split each name-value/argument For i = LBound(tmparr) To UBound(tmparr) tmparg = Split(tmparr(i),"=") p_urldata(LCase(tmparg(0))) = Decode(tmparg(1)) Next End Sub %REM Function GetValue Description: Get value for specified argument. Returns a string containing the value. %END REM Public Function GetValue(argname As String) As String If IsElement(p_urldata(LCase(argname))) Then GetValue = p_urldata(LCase(argname)) Else GetValue = "" End If End Function %REM Function IsValue Description: Check if specified argument was passed in URL or not. Returns boolean value (True or False). %END REM Public Function IsValue(argname As String) As Boolean If IsElement(p_urldata(LCase(argname))) Then IsValue = True Else IsValue = False End If End Function '*** Private function for this class '*** There is no good/complete URL decode function in Lotusscript Private Function Decode(txt As String) As String Dim tmp As Variant Dim tmptxt As String tmptxt = Replace(txt,"+"," ") tmp = Evaluate(|@URLDecode("Domino";"| & tmptxt & |")|) Decode = tmp(0) End Function End Class It is now very easy to use the class to check what values are passed to the agent. Below is a sample agent: Option Public Option Declare Use "Class.URL" Sub Initialize Dim url As URLData '*** Create new URLData object Set url = New URLData() '*** MIME Header to tell browser what kind of data we will return Print "content-type: text/html" '*** Check reqired values for this agent If url.IsValue("name")=False Then Print "Missing argument 'name'." Exit Sub End If '*** Process name argument If url.GetValue("name")="" Then Print "'Name' is empty." Else Print "Hello, " + url.GetValue("name") + "!" End If End Sub It is that easy. If my proposal for a session at ConnectED is accepted, you will about how to use jQuery and Bootstrap to retrieve data in .NSF databases through Lotusscript agents, and I will be using…

4 Comments

#ThrowbackThursday – JMP 101 from Lotusphere 2012

I recorded this video of the session JMP101 IBM Lotus Domino XPages JumpStart at Lotusphere 2012 in Orlando. The conference, later renamed IBM Connect and now renamed again to IBM ConnectED, will take place again in January 2015. Perhaps this video will get you inspired to do some XPages development, or you can show it to your boss as a good example of what you can learn at ConnectED. Hope to see you in Orlando in January!

0 Comments

The joy of programming

I have been programming since the beginning of 1983. I started over 30 years ago with Basic, then went to Pascal after about 3 years. I then in fairly quick succession went to C, Visual Basic, VBA and then a few years later (in 1996) to Lotusscript. Along the way I also picked up Javascript, as well as web design with HTML and CSS (even if it may be questionable to call the latter two "programming languages"). During June and most of July this year, I did not do much/any programming, due to me recovering from surgery. At work I also do more administration work and research, leaving less time for actual programming and even less time to pick up new skills like XPages.  Coming back and starting writing code again made me realize how much I enjoy programming. I miss writing code and solving problems by writing a program that help our users (or me) accomplish something faster and better than before. I enjoy posting code here on my blog, as well as on Stack Overflow and in the developerWorks forums. If I can help someone, like so many have been helping me in the past, at the same time as I get to write code and have fun, that is a double whammy. But even writing code for myself, just for fun and to learn new things is enjoyable to me even after all these years. In a way it is me against the computer. I get to make the machine do what I want by taking a problem or process and breaking it down in smaller and smaller pieces until I have a working solution. Every time I come up with a smarter or more clever way to do something, I get excited and happy. I love learning new things, and in the field of programming (as in the rest of IT), learning never ends. Hopefully I soon will have time to sit down and view some courses at Lynda.com as well as watch some of David Leedy's excellent Notes-in-9 tutorials, to improve my skills and add more/new tools to my toolbox. And to have fun. Happy coding!

0 Comments

Code snippet – DateClass

Here is a small Lotusscript class I wrote some years ago. I use it in a number of other classes where I need to use date functionality of different kind. For example, I have a class that communicates with a FoxPro database, using a COM object. Some of the methods in that class uses XML while other just pass a few arguments to the COM object. The COM object expects the date values to be in ISO 8601 format (yyyy-mm-dd). In addition, sometimes the date comes from a field in a Notes document where they usually are stored in US format (mm/dd/yyyy), sometimes it is the current date. So I decided to create a this class to just make the code cleaner and to avoid having to do the same conversions over and over again. This class can of course be extended with more functionality if you like. I simply put the class in a script library called "Class.Date" and then use that script library in my other classes or agents. Class DateClass Private dt As NotesDateTime Public ErrorMsg As String Public Sub New(value As Variant) Dim datestring As String ' *** Check what data type was passed and take actions. ' *** If value is blank or Nothing, use today's date. Select Case Typename(value) Case "EMPTY" : datestring = Format$(Today(),"Short Date") Case "STRING" : If Fulltrim(value) = "" Then datestring = Format$(Today(),"Short Date") Else datestring = value End If Case "DATE" : datestring = Cstr(value) End Select ' *** Also check that the value is a valid date If Isdate(datestring) = False Then ErrorMsg = "Class.Date:New() - '" & datestring & "' received is not a valid date." Set dt = Nothing Exit Sub End If ErrorMsg = "" Set dt = New NotesDateTime(datestring) End Sub Public Function DateOnly As String ' *** Return date-part only, in format selected by the system DateOnly = dt.DateOnly End Function Public Function DateOnlyISO As String ' *** Return date-part only, in ISO 8601 (big endian) standard format DateOnlyISO = Format$(dt.dateOnly,"yyyy-mm-dd") End Function Public Function DateOnlyUS As String ' *** Return date-part only, in US (middle-endian) format DateOnlyUS = Format$(dt.dateOnly,"mm/dd/yyyy") End Function End Class And this is how I use the class, this is the first few lines of a function in another script library: Public Function GetPolicyData(Byval policynumber As String, Byval lossdate As Variant) As Integer Dim DoL As DateClass Dim result As Integer Set DoL = New DateClass(lossdate) If DoL Is Nothing Then '*** Display message, including error message from DateTime class MsgBox = |Failed to initialize New DateTimeClass with LossDate "| & _ lossdate & |". | & DoL.ErrorMsg Exit Function End If '*** Call COM object with policy number and date of loss in ISO 8601 format result = object.GetPolicyData(policynumber, DOL.DateOnlyISO()) ... There you have it. Easy, isn't it?

0 Comments

In memory of Tim Tripcony

A couple of hours ago, I was reached by the terrible news that Tim Tripcony is no longer with us. I have known Tim for several years, and meeting him at Lotusphere (later Connect) was always a treat. He is one of the most brilliant programmers I have met, and he always had time to talk to me about some question I had or just discuss some technical concept. Every time I met Tim, it felt like a little of his intelligence rubbed off on me. A couple of years ago, I started looking at XPages, and when I ran into a problem, I asked Tim for some help on Skype or Sametime (don't remember now which one it was). As the helpful and generous person he was, he took the time out of his busy day to help me with my problem. Tim was one of the early adopters and champions of XPages, and I have been reading his excellent blog and been to his sessions at Lotusphere. He is one of the developers I admired the most, and that is no easy feat, with all the brilliant individuals we have in the ICS ("Yellowsphere") community. Tim, thanks for all your help and ideas over the years. My thoughts goes out to your family, your friends and co-workers, and to everyone else who were fortunate to know you. You will be missed.

2 Comments

Code snippet – Disable agent using external file

Yesterday I was asked to create a way to let us disable agents running on a Domino server in an easy way before the Domino server comes back from a crash. The reason for this request is that for a while we have been having one particular agent crash, taking the whole Domino server down with it. It only happens occasionally, and seems to be related to the document being processed. When the server comes up after a crash like that, a consistence check is done, then the agent manager launches the agent again, causing the server to go down again. I added code to the offending agent, so it would flag the document before processing and un-flag after processing is done. This way, when the agent encounters an already flagged document, it will be skipped as it was processed during a previous crash. For some reason this did not work yesterday morning, when one of those rare corrupted(?) documents was encountered. The logic in the code was faulty, because the document was of a new type, so it was never flagged as being processed. The same document was processed over and over again, taking the server down every time. So I simply created two functions, put them in a global script library where I keep utility functions used in many places, and added 3 lines of code to each agent where I wanted this functionality. The first function is simply to check if a specified file exists. I am using en error handler to catch any error (for example missing directory). Function FileExists(filename As String) As Boolean On Error GoTo errHandler If Dir$(filename)<>"" Then FileExists = True Else FileExists = False End If exitFunction: Exit Function errhandler: FileExists = False Resume exitFunction End Function The second function is the one where I check for the existance of a file named the same as the agent, with an extension of .disabled. If that file does not exist, I check for a file with the extension .enabled. If that file is missing, I simply create a blank file with that name. This way, the first time any agent is executed, the file will be created for us, and I don't have to sit and manually create them all. Function DisableAgent() As Boolean Dim session As New NotesSession Dim agentname As String Dim filename As String agentname = session.CurrentAgent.Name filename = "D:\NotesAgentControlFiles\" + agentname + ".disabled" If FileExists(filename) Then DisableAgent= True Else filename = "D:\NotesAgentControlFiles\" + agentname + ".enabled" If Not FileExists(filename) Then Open filename For Output As #1 Print #1, "" Close #1 Print "Created control file " & filename End If DisableAgent= False End If End Function Finally, in each agent I want to be able to disable like this, I add this code in the beginning: '*** Check if disable-file exists, exit in that case If DisableAgent() Then Exit Sub End If Just a few lines of code, but hopefully it will save someone a few minutes of work. Of course, you…

0 Comments

Code snippet – jQuery

This morning I was working on a web application, and I came up with a pretty neat and simple little solution. So I just wanted to share it, in case anyone else need something similar. I have a webpage with an HTML form. Each input tag has an attribute called notesfield, matching the name of the field in Notes where the value is stored: <div class="col-md-3"> <label>First Name</label> <input class="form-control" type="text" notesfield="FirstName" value="" /> </div> <div class="col-md-2"> <label>Initial</label> <input class="form-control" type="text" notesfield="MiddleInitial" value="" /> </div> <div class="col-md-3"> <label>Last Name</label> <input class="form-control" type="text" notesfield="LastName" value="" /> </div> Then I created a simple function that will call an agent on the Domino server, which will return all the fields on the specified document as JSON. This function is called after the HTML page is fully loaded. function loadNotesFields(docunid) { var notesfieldname = ""; $.ajax({ url: "/database.nsf/ajax_GetNotesFieldFields?OpenAgent", data: {"NotesUNID":docunid}, cache: false }).done(function(data) { $('input[notesfield]').each(function() { notesfieldname = $(this).attr("notesfield"); $(this).val(data[notesfieldname]); }); }); } The function is actually extremely simple, and here you can see the power of jQuery. What I do is to perform an Ajax call to a Domino URL, passing a UNID to the agent to use in the lookup. I set cache to false, to avoid the browser from reusing previously retrieved data (this is a good thing to do if the data retrieved can be suspected to change frequently). The jQuery .ajax() functions returns the JSON in the data object, and when the call is done, the callback function loops through each input element with an attribute of notesfield, reads the value of said attribute and then sets the value of the input element to the corresponding Notes value. The only thing left is to write the agent that will return the JSON. It could look something like this: Dim urldata List As String Sub Initialize Dim session As New NotesSession Dim webform As NotesDocument Dim db As NotesDatabase Dim doc As NotesDocument Dim urlstring As String Dim urlarr As Variant Dim urlvaluename As Variant Dim i As Integer Dim json As String Set webform = session.DocumentContext '*** Remove leading "OpenAgent" from Query_String urlstring = StrRight(webform.Query_String_Decoded(0),"&") '*** Create list of arguments passed to agent urlarr = Split(urlstring,"&") For i = LBound(urlarr) To UBound(urlarr) urlvaluename = Split(urlarr(i),"=") urldata(urlvaluename(0)) = urlvaluename(1) Next Set thisdb = session.CurrentDatabase '*** Create content header for return data Print "content-type: application/json" '*** Get Notes document baed on NotesUIND argument Set doc = db.GetDocumentByUNID(urldata("NotesUNID")) '*** Build JSON for all fields in document except $fields json = "{" + Chr$(13) ForAll item In doc.Items If Left$(item.Name,1)"$" Then json = json + |"| + item.Name + |":"| + item.Text + |",|+ Chr$(13) End If End ForAll '*** Remove trailing comma and line break json = Left$(json,Len(json)-2) json = json + "}" '*** Return JSON Print json End Sub Happy coding!

6 Comments

Connect 2014 – More about the sessions

The sessions at Lotusphere/Connect are focusing more and more on XPages each year. I don't think there are really any sessions about classic Notes development this year. Currently I am not able to use XPages at work, due to the client environment we run (Lotus Notes 8.5.2 Basic in Citrix), but the sessions are still great (see video below) and I am learning new things every year. Hopefully we will soon switch the client to IBM Notes 9.0 so I can use all this knowledge to build some really cool and useful applikations for our users. [embedyt]http://youtu.be/0ViUTfAzoTo[/embedyt] I am also going to some admin-related sessions, this is an area I am trying to get more knowledgeable in, after our network/Domino administrator left in 2012. A frequently comparison is that going to Lotusphere is like trying to drink from a fire hose. There is so much info that you can't take it all in. You will soak up a lot of good and useful information, and if there are things you don't understand, you can ask the speakers after the sessions, or even later in the week, as you surely will see them around in the hallways, at other sessions. You can also ask questions to the Best Practices speaker at Gurupaloza (Thursday 10am) and the IBM developers at Ask the Developers (Thursday 1.30pm) sessions. The latter is often tongue-in-cheek called Beat the Developers. If you have less technical questions, for example about the future direction of a product or if a particular feature is planned for a future release, don't miss the Ask the Product Managers (Thursday 11.15am). Talking about those sessions, here is a friendly reminder: keep the right questions in the right sessions. Every year there is someone who asks questions about future directions of products at Gurupaloza or asks about a particular problem/function in the Product manager session. Doing this irritates the rest of the audience, and causes someone else not to get the time to ask his/her question. Also, if you have a very specific technical question, don't bring that to these sessions, go see the developers in the lab instead during the week. Nobody is interested in the printing problem one user at your office is having with one specific type of printer... That is a support question. The most important rule however, is one question per user. If you have two questions, go to the back of the line and wait for your turn. Don't try to hog the microphone.

0 Comments

Code – Mask text to remove PII

Sometimes you need to remove personal identifiable information (PII) from the data you present in an application or on a web page. In the last couple of weeks this issue popped up twice, including one application which needs to be be HIPAA compliant. One solution is to mask any personal identifiable data so that the recipient can still verify the information, without sending it all in clear. I am sure you all seen this on for example credit card statements, with only the last 4 digits of your credit card number displayed. I wrote a simple  Lotusscript function to do this, and I thought I would share it so others can use it as well. You pass a string to mask, the number of characters to leave un-masked and where the unmasked characters should be displayed ("B" for beginning or "E" for end). MsgBox masktext("TexasSwede",3,"B") This line would display Tex******* MsgBox maskText("1234567890",4,"E") This line would display ******7890 Enjoy!   %REM Function maskText Description: Masks a text with asterisks, leaving the num first or last characters visible. Direction is "B" (beginning) or "E" (end). Created by Karl-Henry Martinsson - texasswede@gmail.com %END REM Function maskText(value As String, num As Integer, direction As string) As String Dim tmp As String Dim i As Integer If Len(value)>num Then If Left$(UCase(direction),1)="B" Then ' Start at the beginning tmp = Left$(value,num) For i = num+1 To Len(value) tmp = tmp + "*" Next Else ' Start at the end tmp = Right$(value,num) For i = Len(value) To num+1 Step -1 tmp = "*" + tmp Next End If Else tmp = value End If maskText = tmp End Function

4 Comments

API for ZIP Code Distance, Radius, and more

  The other day I stumbled on a really cool website, where they offer a free API to calculate distance between two US ZIP codes, all ZIP codes in a specific radius from a given ZIP code, as well as a few other ZIP code related functions. The results can be returned in different formats, like JSON, XML and (in some cases) CSV. Highly recommended! The URL is http://zipcodedistanceapi.redline13.com/ I am considering creating a Lotusscript class for some of these functions, so stay tuned!  

2 Comments

25 years in the IT industry

Today it is exactly 25 years since I started my first real job in the IT industry. On September 19, 1988 I started working at Microsoft in Sweden as employee #42, right out of 12th grade of school. So how did I end up working at Microsoft at age 19? Well, I had a bit of bad luck, which turned into good luck. Let me explain. :-) I started programming in 7th grade. In the late fall of 1982, a computer club was founded in my school. When we came back to school after Christmas break, in January 1983, some older students taught some classes in BASIC in the evenings. Attending those classes were a requisite for getting the magnetic card that gave us access to the computer room (as long as there were no regular classes taking place there). In preparation of the classes starting, I went to the library and picked up a book on programming the ABC 80 computers we had in school. I started learning programming by writing code by hand in a notebook, to understand the concept. I spent the Christmas break learning BASIC, so when the classes started in January, I had a pretty good understanding of the concept of programming. A couple of years later we got another type of computer in school, and I switched to Pascal as the programming language of choice. I spent on average 3 or 4 hours in the computer room each day (during lunch breaks and after school) for the next 5 1/2 years... I even managed to convince the school to let me borrow one of the computers and take it home during one Christmas break, as I was working on a big project. After finishing what's in Sweden is called gymnasium (equivalent of High School in the US), I was not motivated to spend additional 4 years or more going to university. However, I found a one-year specialty course in Systems Programming and Computer Science, where they crammed 2+ years into one year, with 8-hour days five days/week. I applied and was accepted. However,after a couple of weeks, the assistant principal (who was also one of our main teachers) came in and told us that the class had to be cancelled. The class was simply too small, and they had not been able to get any more students to apply. The class was postponed and would start over in January 1989. In the mean time we were encouraged to find an internship or entry-level job in the IT industry. I picked up the yellow pages section of the phone book and looked up computer companies. Being a person thinking outside the box, I started going through the companies in reverse order. I figured that anyone else in the class would start from the beginning. I started cold-calling some companies, and after a few calls, I got a hit. This company called Microsoft was interested, they needed someone in tech support, to answer calls from customers and solve their problems. I had not really heard much about Microsoft at this…

2 Comments

Class for Domino Directory lookups

In many of my Notes programs, I need to perform lookups into the Domino Directory (the database formerly known as Name and Address Book or NAB). So my solution was to create a class that handle those lookups for me, and exposes the most common lookups as separate methods. We have a slightly modified version of names.nsf, with a few added fields. One of them is what we call ParallelID, which is the user's ID in a system called (surprise!) Parallel. Since I perform that lookup all the time, I created a separate method for that one called GetParallelID(). Same with manager lookup for a user, I created GetManagerName() for that. The methods you probably will use the most are GetText() and GetValue(). Since I think this class could come in handy for others, here it is. Enjoy! Option Public Option Declare Class NotesAddressBook Private NABdb As NotesDatabase Private server As String Private nabname As String Public silent As Boolean Public Sub New(servername As String) me.silent = false Call LoadNABdb(servername) End Sub Public Function GetNABdoc(personname As String) As NotesDocument Dim NABview As NotesView If NABdb Is Nothing Then Call LoadNABdb("") End If If Not NABdb Is Nothing Then Set NABview = NABdb.GetView("PeopleByFirstname") Set GetNABdoc = NABview.GetDocumentByKey(ShortUserName(personname)) Else Set GetNABdoc = Nothing End If End Function Public Function database() As NotesDatabase If NABdb Is Nothing Then Call LoadNABdb("") End If If Not NABdb Is Nothing Then Set database = NABdb End If End Function Public Function GetValue(personname As String, fieldname As String) As Variant Dim NABdoc As NotesDocument Set NABdoc = GetNABdoc(personname) If NABdoc Is Nothing Then If me.silent = False then Msgbox "No document found for '" & personname & "' in " & nabname & " on " & server & ".",,"NotesAddressBook::GetNABdoc()" End If GetValue = "" Else GetValue = NABdoc.GetItemValue(fieldname) End If End Function Public Function GetText(personname As String, fieldname As String) As String Dim tmp As Variant tmp = GetValue(personname, fieldname) If IsArray(tmp) Then GetText = CStr(tmp(0)) Else GetText = CStr(tmp) End If End Function Public Function GetName(personname As String, fieldname As String) As NotesName Dim tmpValue As String tmpValue = GetText(personname, fieldname) If tmpValue <> "" Then Set GetName = New NotesName(tmpValue) End If End Function Public Function GetNameByParallelID(parallelid As String) As String Dim view As NotesView Dim doc As NotesDocument Dim tmpValue As String Set view = NABdb.GetView("(LookupUserID)") Set doc = view.GetDocumentByKey(parallelid) If doc Is Nothing Then Exit Function End If tmpValue = doc.GetItemValue("FirstName")(0) & " " If doc.GetItemValue("MiddleInitial")(0)<>"" Then tmpValue = tmpValue & doc.GetItemValue("MiddleInitial")(0) & " " End If tmpValue = tmpValue & doc.GetItemValue("LastName")(0) If tmpValue <> "" Then GetNameByParallelID = tmpValue End If End Function Public Function GetCommonName(personname As String, fieldname As String) As String Dim tmpName As NotesName Set tmpName = GetName(personname, fieldname) If Not tmpName Is Nothing Then GetCommonName = tmpName.Common End If End Function Public Function GetManagerName(personname As String) As String GetManagerName = GetCommonName(personname, "Manager") End Function Public Function GetParallelID(personname As String) As String GetParallelID = GetText(personname, "ParallelID") End Function Public…

1 Comment

Some personal thoughts and a big Thank You

Last week, Volker wrote this excellent article about Tomas Duff (a.k.a. Duffbert). Then yesterday the news reached me about the sudden death of Rob Wunderlich, a long-time member of the Lotus community. I had already started on a post -- in preparation of my upcoming 25 year anniversary of becoming an IT professional -- where I was going to acknowledge a number of people who meant much to me and who were important in making me to what I am today. I have decided to post this text a bit earlier than originally planned. There are so many people who helped me and supported me over the years, and without them I would not be where I am now professionally. Some took a chance on me and gave me jobs where I grew professionally, others were more like mentors or inspirations, and some were teaching me how to do things with computers or in code. I know I am probably forgetting many who deserve to be mentioned. But I want to thank the following: Tonny Olsson - my cousin who worked at Hewlett-Packard and let me see my first computer (complete with a plotter and an acoustic modem he used to connect to HP from our house) in or around 1975. He also introduced me to the world of HP calculators and RPN. Peter Nilsson - my childhood friend and classmate, who introduced me to Basic programming when he got a VIC-20. We spent an evening (right after he got it) entering a program from the handbook, but we did not get it to work that day. Later on we got some programs working. Henry Jacobsson - My teacher in computer science/programming in High School, who allowed me write my code in Turbo Pascal for CP/M-86 instead of the special language COMAL (a mix between BASIC and Pascal). He also taught me the basics of how to plan/design an application. I also want to thank Henry for not kicking me out when I hacked his systems administrator account and assigned myself 1MB of storage on the 30MB hard disk we had on the server. Normally each student got 4kB, but I wanted more. :-) I also want to thank several of the older students in the school's computer club, who helped us younger students when we had questions. I want to mention Hjalmar Brismar, Petter Aaro and Matthias Bolliger, who were always there with advise and knowledge. Arne Josefsberg - head of tech support at Microsoft, he took a chance and gave me a job without me having touched any Microsoft program previously. Rolf Åberg, Magnus Andersson, Anna Söderblom and Micael Dahlquist - also at Microsoft. They helped me learn all kind of new things, from Windows programming using C and the Windows SDK to regular C programming using QuickC, from Excel to Word for DOS. I also ended up wothing with Micael at another job a few years later. Per Engback and Ingvar Gratte - my two main teachers at the systems programming class. Despite this being just a one-year class, I learned plenty, especially C programming and Unix. Krister Hanson-Renaud and Harald Fragner - two programmers/hackers who inspired…

6 Comments

My IBM Notes project on GitHub

I decided to play around a little, and as an experiment put up one of my Notes projects on the open source repository GitHub. You can see the result here: http://github.com/TexasSwede/Class.MailMerge This is a script library in Lotusscript to create documents based on a source document and a template document. I have blogged about it before, but I added some functionality to it, and thought it would be easier for people to download a complete database.  

0 Comments

jQuery – An Overview

Yesterday my boss asked me about a simple overview/tutorial explaining jQuery, Bootstrap and some other web technologies, and how they work together. I decided to also post the result on my blog, so here is the first part. You may recognize some code from a previous blog entry. jQuery is a currently very popular Javascript framework/library. There are other ones, like Dojo (used by IBM in XPages) and YUI (originally developed by Yahoo), but jQuery is right now at the top when it comes to usage. jQuery contains the plumbing behind the scene, it contains functions to let the different elements on the page talk to each other and interact, for example trigger events on click or change. It also have functions to hide and show elements (either directly or fade in or out). One of the big benefits with jQuery is that many functions are much easier to do than in traditional Javascript. It also addresses browser inconsistency, so you don't have to write different code for Firefox and Internet Explorer. jQuery is Javascript, just packaged in a nice library and simplified. There are also UI components and mobile components, found in jQuery UI and jQuery Mobile. Here are a couple of examples, comparing plain Javascript with jQuery: http://blog.jbstrickler.com/2010/06/vanilla-javascript-vs-jquery/. jQuery ties into the DOM (Document Object model) of the browser/webpage in a very easy-to-use way. The way elements are addressed is identical to how you do it in CSS, using . (dot) for classes and # for individual elements. It is not hard to start with jQuery. You do not even have to host the library on your own server, several companies (including Microsoft and Google) host jQuery (as well as other libraries and frameworks) in what is called a CDN (Content Delivery Network). You simply include a line of code in the head section of your HTML, telling the browser to load jQuery from a specified location, and you are all set: <head> <title>Hello, jQuery</title> <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min.js"></script> </head> Notice that you don't use http: or https: at the start of the URL. This is a trick that makes it work both on a http or a https site. However, if you have the code locally in the file system on your computer (like many do before uploading the html file to a server), you must add http: at the beginning for it to load. Let take a look at our first jQuery example. Below we have a very simple piece of HTML code: <body>     <button id="btnSave">Save</button>     <div id="messageBox"></div> </body> What we want to do is that when the button is clicked, a message should be displayed in the div with ID messageBox. That is done with the following piece of jQuery: $("#btnSave").click( function() { $("#messageBox").html("You clicked the Save button."); }); What this do is to replace everything inside the div with the text/HTML code we specify. The second line is the code to execute when the event specified triggers/fires. You can put triggers on almost any element, and…

1 Comment

Export Notes view to Excel – with multi-value fields

A few days ago, a question was asked on StackOverflow about how to export the content of a Notes view to Excel. The caveat was that some columns contained multiple values, but not on all documents. To solve this, I wrote a Lotusscript class that will export view data as either CSV or as an HTML table, both can then be saved to a file and opened in Excel. I am posting the code below. Enjoy!   %REM Agent View Export Created Mar 27, 2013 by Karl-Henry Martinsson Description: Code to export a specified view as CSV. Copyright (c) 2013 by Karl-Henry Martinsson This code is distributed under the terms of the Apache Licence Version 2. See http://www.apache.org/licenses/LICENSE-2.0.txt %END REM Option Public Option Declare Class RowData Public column List As String Public Sub New() End Sub Public Sub SetColumnHeader(view As NotesView) Dim viewcolumn As NotesViewColumn Dim cnt As Integer ForAll vc In view.Columns Set viewcolumn = vc column(CStr(cnt)) = viewcolumn.Title cnt = cnt + 1 End Forall End Sub Public Sub SetColumnValues(values As Variant) Dim cnt As Integer Dim tmp As String ForAll v In values If IsArray(v) Then ForAll c In v tmp = tmp + c + Chr$(13) End ForAll column(CStr(cnt)) = Left$(tmp,Len(tmp)-1) Else column(CStr(cnt)) = v End If cnt = cnt + 1 End ForAll End Sub End Class Class CSVData Private row List As RowData Private rowcnt As Long %REM Function New Description: Open the view and read view data into a list of RowData objects. %END REM Public Sub New(server As String, database As String, viewname As String) Dim db As NotesDatabase Dim view As NotesView Dim col As NotesViewEntryCollection Dim entry As NotesViewEntry Dim colcnt As Integer Set db = New NotesDatabase(server, database) If db Is Nothing Then MsgBox "Could not open " + database + " on " + server,16,"Error" Exit Sub End If Set view = db.GetView(viewname) If view Is Nothing Then MsgBox "Could not access view " + viewname + ".",16,"Error" Exit Sub End If Set col = view.AllEntries() rowcnt = 0 Set entry = col.GetFirstEntry() Set row("Header") = New RowData() Call row("Header").SetColumnHeader(view) Do Until entry Is Nothing rowcnt = rowcnt + 1 Set row(CStr(rowcnt)) = New RowData() Call row(CStr(rowcnt)).SetColumnValues(entry.ColumnValues) Set entry = col.GetNextEntry(entry) Loop End Sub %REM Function CSVArray Description: Returns a string array of CSV data by row %END REM Public Function CSVArray() As Variant Dim rowarray() As String Dim textrow As String Dim cnt As Long ReDim rowarray(rowcnt) As String ForAll r In row textrow = "" ForAll h In r.column textrow = textrow + |"| + Replace(h,Chr$(13),"\n") + |",| End ForAll rowarray(cnt) = Left$(textrow,Len(textrow)-1) cnt = cnt + 1 End ForAll CSVArray = rowarray End Function %REM Function HTMLArray Description: Returns a string array of HTML data by row %END REM Public Function HTMLArray() As Variant Dim rowarray() As String Dim textrow As String Dim cnt As Long ReDim rowarray(rowcnt) As String ForAll r In row textrow = "" ForAll h In r.column textrow = textrow +…

36 Comments

Should everyone be a programmer?

For years, there has been a debate if anyone can (or should) learn programming or not. While reading the Notes and Javascript groups on LinkedIn, as well as the Notes forums on IBM developerWorks, I have read more than one post where someone wants to learn Javascript or Notes programming, but don't have any programming experience/knowledge. Can anyone learn to program? No. I would say half the population could learn at least the basics and mechanics of programming.  So should everyone of those learn to program? In my opinion, absolutely no. Remember, it is widely considered that it takes about 10 years or 10,000 hours to be good at skills like  programming or playing an instrument. A majority will not invest that time in practicing unless they really have the passion. I think you also need to have a special mindset to become a good programmer. If you learn to program just because you think it is a good career, or something that will pay you a decent salary, but you don't have the deep interest or the right aptitude, then you will most probably not be a good programmer. I'm still wondering: why do people who can't write a simple program even entertain the idea they can get jobs as working programmers? Clearly, some of them must be succeeding. Which means our industry-wide interviewing standards for programmers are woefully inadequate, and that's a disgrace. It's degrading to every working programmer. At least bad programmers can be educated; non-programming programmers are not only hopeless but also cheapen the careers of everyone around them. Jeff Atwood, Coding Horror   Passion A sign of a good or great programmer is passion. You must be really interested in it to be good at it. Anyone can learn to drive a car, but you need passion and dedication to become a Nascar or F1 driver. This is what drove me to spend hours every day after school in the computer room, until the school closed for the night and I was kicked out. I did not have a computer at home, so this (together with the breaks between classes) was my only chance to practice programming. I wanted to learn, to be good at it, to get the computer to do what I wanted it to do. I wanted to find a problem and solve it, and then find a better way to solve the same problem, until I was satisfied I had the best solution I was able to create.   Problem Solving Skills When you are a programmer, no matter what level, you need to be able to solve problems. If you are a junior programmer, you might get a task assigned to you by a more senior developer, but you still need to break down that task into smaller parts and solve the problem. After a fair bit of trial and error I've discovered that people who struggle to code don't just struggle on big problems, or even smallish problems (i.e. write a implementation of a linked list). They struggle with tiny problems.…

5 Comments

Track your application changes/updates

A while back I created a little tool for my own use, to keep track of my Notes templates and their version numbers. I also developed a tracking database where we track not only changes to Notes applications, but also other changes to our environment. Things like modifications to Domino Directory and Active Directory, as well as other meta data changes, configuration changes, etc. I decided to merge those two applications, and create one focused on tracking changes in Notes templates/applications. I thought perhaps other developers could use this application, so feel free to download the template. This is what the application looks like: After you download the template, use it to create a new Database. In the new database, open the Applications Settings document and enter the application name (should be "Change Tracking"). Also modify the list of systems, this is where the dropdown box in the actual form get its categories from. The only required is one called "System (IBM/Lotus)", but I also suggest to create one called "System (your company name)". You can see an example of the configuration document below. You can now go ahead and load the list of local Notes templates. This is done in the System/Tools section as well. The next step is to open all templates not containing a version field and update them. Go to the section called "No Version"  and open the view, then open each template and select the correct system and enter the application name. Then create the version field using the Template action menu, where you find the action 'Create VersionField'. Enter your initial version number (major, minor, release and build), then save the template document. Now when you have all your templates updated with version numbers, you can start tracking changes using the "Add New Entry" button. Hopefully the application is fairly self-explanatory. The template is using ODS 51, so you need Notes 8.5 or higher to open it. There is also one known issue that I haven't had time to fix yet. If you have the setting "Auto-increase build when adding entry" to "Yes", you have to close and reopen the template document to see the new entry. If it is set to "No", it shows up immediately. If anyone fixes this, let me know and I will update the template on my side too. Update: I have fixed the issue. It was as easy as removing the line  uidoc.EditMode = False  in the 'Add New Entry' action on the 'Template' form. The download has been updated.  Update 2: I have also modified the template to use DateTime fields in a few places where I used regular text fields. Thanks Kenneth for pointing that out!

6 Comments

End of content

No more pages to load