Don McNally
Thoughts on technology, Lotus Notes/Domino, Catholic youth ministry, music and whatever else pops into my head.
Saturday, May 18, 2013
Building a Softball Bat Rack from PVC Pipe
I used 3" diameter PVC for my holder. I had 1.5" at home but that seemed too small. When I looked at it at the store, the 2" seemed too small also, but I think it might have worked. Anyway, I cut a 30" length and was able to put slots for nine bats in it. This is the pattern I created for each slot.
It should be about 4.25" tall when printed. I found it worked best for tracing on the pipe when I printed it on card stock. Cut along the outside edge of the black line and it will be about the right size. I used a scrolling saw to cut each slot. Getting it started was the hardest part but it only took me a couple of tries to get a good feel for how to do it. And only one bent blade! :)
I left about 1" between the top parts (the wider part, for the knob of the bat) of each slot, and I cut one before I traced the next one in case my cut got a little away from me. Make sure you line up the tops and bottoms of the slots so the bats all hang the same.
To attach them to the chain link fence, I used a hose clamp that fit up to a 4" pipe and put the clamp in between slots. Maybe I'll add a picture of that later. Hang it so that the bottoms of the slots are along the bottom of the pipe so that the bats hang straight down.
It took about 90 minutes and about $15 to make two of these. I just put them up today and our first game isn't until Tuesday. I think the girls - and the others who use those fields - will be pleasantly surprised!
Friday, March 15, 2013
XPages: Returning to the Prior Page
I have a button in my left navigator that opens an XPage for processing documents with a bar code scanner. That XPage does not include the navigator; it's like a new document. Once the user is done processing, they click a Finished button and I wanted them to go back where they came from.
My method of saving the page is the same as Brad's: a sessionScope variable. This is the code in an Execute Script action in the button before it opens the new page:
var curURL = context.getUrl().toString();As I look at it, perhaps view.getPageName() would be more efficient, but I didn't know about it at the time and I can't argue with success. :)
sessionScope.put("bundleProcReturn",@RightBack(curURL,"/"));
In the Finished button, the Open Page action computes the Page Name value as:
sessionScope.get("bundleProcReturn");It's really handy that so many things in XPages can be computed. Once you understand that, you can use that feature to have your applications do exactly what you want. It takes a little more time to keep track of things like the page you were on when you launched an action, but the user experience can be so much better - and easier for users transitioning from a Notes client app to the XPages version of the app.
That isn't to say I want to make my XPages apps look and behave like a Notes client app, but there are times when those behaviors are "expected" or "normal".
Saturday, February 2, 2013
XPages: Detecting and Logging Field Value Changes
valueChangeListener code as a managed bean by Tony McGuckin, which was linked from
valueChangeListener implementation code from Declan Sciolla-Lynch, which pointed to
A better way to update log fields by Chris Toohey
I used my "R&D skills" to set up the code in my application and was excited when it seemed to work right off the bat. I moved it to the QA environment and then things started going bad. I kept getting Null Pointer Exception errors when I tried to add and update documents. I eventually realized a) I hadn't tried to create documents on the development server, and b) I hadn't tried to update documents that had never been updated previously. Neither the valueChangeListener bean nor the addToList function were working if the starting value of the field was an empty string.
I still need to figure out how to identify an empty string in those fields to properly handle that situation, but I got around it by making a few changes in the code and by adding a default value to a field so it doesn't start as blank.
The "do something useful" code to put in Tony's bean ('c' is a UIComponent object):
// Get the name of the field that changedCode to read the old and new values and write them to a log field named ActivityHistory:
String changedExpression = c.getValueBinding("value").getExpressionString();
int firstDot = changedExpression.indexOf(".") + 1;
int closingBracket = changedExpression.indexOf("}");
String changedField = changedExpression.substring(firstDot,closingBracket);
// Get the old and new values in the field
String oldValue;
try {
oldValue = valueChangeEvent.getOldValue().toString();
} catch (NullPointerException npex) {
oldValue = "";
} catch (Exception ex) {
System.out.println("Major failure");
ex.printStackTrace();
oldValue = "";
};
String newValue = valueChangeEvent.getNewValue().toString();
MapviewScope = ExtLibUtil.getViewScope();
HashMapoldValuesMap = new HashMap ();
HashMapnewValuesMap = new HashMap ();
if (viewScope.containsKey("oldValues")){
oldValuesMap = (HashMap) viewScope.get("oldValues");
newValuesMap = (HashMap) viewScope.get("newValues");
}
oldValuesMap.put(changedField, oldValue);
newValuesMap.put(changedField, newValue);
viewScope.put("oldValues", oldValuesMap);
viewScope.put("newValues", newValuesMap);
var dNow = @Now();Updated addToList code (I used 'first' and 'last' instead of 'prepend' and 'append'):
if (viewScope.containsKey("oldValues")){
var oldValues:java.util.HashMap = viewScope.get("oldValues");
var newValues:java.util.HashMap = viewScope.get("newValues");
for (key in oldValues.keySet()) {
actText = "Field " + key + " changed from '" + oldValues.get(key) + "' to '" + newValues.get(key) + "' - " + @Name("[CN]",session.getEffectiveUserName()) + " on " + @Text(dNow);;
addToList(bundleDoc,"ActivityHistory",actText,"last");
}
}
function addToList(datadoc:NotesXspDocument, fieldname:string, newVal:string, addPos:string) {
var newArr = new Array(datadoc.getItemValue(fieldname));
switch (addPos) {
case 'first':
newArr.unshift(newVal);
break
case 'last':
newArr.push(newVal);
break
}
datadoc.replaceItemValue(fieldname,newArr);
return true;
};
If anyone can explain what I missed with fields that start with a value of empty string, I'd appreciate it as I'm very early in my XPages and Java learning. In the mean time, I hope this proves useful to others.
Thursday, January 31, 2013
New Design
Tuesday, January 24, 2012
More Excel Code: Inserting and Copying Rows
After some experimentation and some dumb luck stumbling on information that referred to methods I wasn't aware of in the COM interface, I came up with the code below.
Sub CopyFormulas(xlSheet As Variant, rowNum As Integer, firstCol As Integer, lastCol As Integer)
Dim x As Integer
Dim cellVal As String
xlSheet.Activate
xlSheet.Cells(rowNum-1,1).EntireRow.Copy
xlSheet.Cells(rowNum,1).EntireRow.Select
xlSheet.PasteSpecial(7)
xlSheet.Application.CutCopyMode = False
For x = firstCol To lastCol
cellVal = xlSheet.Cells(rowNum,x).Formula
If Left(cellVal,1) <> "=" Then
xlSheet.Cells(rowNum,x).ClearContents
End If
Next
End Sub
provided by Julian Robichaux at nsftools.com.
This takes the Worksheet object you pass it, copies the content and format of the row above your current row into your current row and then removes the contents of a defined set of columns if they are not formulas. The problems I had were that the copy was not done in the proper place and I wasn't maintaining the formulas and formatting of the row I was copying.
The copy location was solved by adding the xlSheet.Activate line. My code was writing data to three different worksheets (one each for month-to-date, quarter-to-date and year-to-date) so I found that adding that line made sure the proper sheet was active.
The formatting issue was resolved by a combination of two methods. One is .PasteSpecial(7) , where the 7 represents the xlPasteAllExceptBorders paste option. The other is the .ClearContents method. I was using the .Paste and .Clear methods initially. I suspect I could use the .Paste and .ClearContents methods, since I think the .Clear method was the culprit that was deleting the formats, but I am going to leave it the way it is since a) I know it works, and b) the .PasteSpecial method indicates what I am really trying to do.
By the way, the code to insert a row is xlSheet.Cells(row,col).EntireRow.Insert.
Enjoy! Now, how many times do you think I'll find this blog post when I search for this in the future? :)
Tuesday, June 28, 2011
Inheriting Field Values for Names Fields
The fields I was trying to inherit were names fields. And the two "parent" forms used different names for the names I wanted to inherit. On the form that worked, the fields were named the same on that form and on the child form I was using for my new document. Aha! I changed the field name on the child form to match that of the form that didn't work, and that started working; of course, that also broke the other form that worked previously.
What I ended up doing was adding a Computed For Display field to the child form that was named the same as the field on the form that didn't work. It has a value in it when a new child is created and is blank at all other times. I then reference that field in the formula for my name field and it works for both forms.
I couldn't find anything that said Names fields inherit differently than other fields. but they apparently are different. After spending about 4 hours trying to figure it out, I figured I'd write it out here, just in case it comes up again.
Thursday, March 31, 2011
Barcodes in Lotus Notes Applications
If you have ever wanted to implement barcoding in a Notes application, it may be easier to do so than you think. You really need four things to make it work:
- A barcode font that is deployed to machines that will be printing the things (eg. paper or labels) that have the barcode on them.
- LotusScript code to generate the field value for the barcode. Simple barcodes, like Code 39, just need start and stop characters before and after the number you are putting on a barcode, but they have a limited character set. More complex barcodes, like Code 128, require start and stop characters as well as check digits but are able to include the alphanumeric character set.
- A barcode scanner.
- An input form to handle the scanner’s output and cause processing to happen.
Though the application I am working on is encoding a 12-digit number, we are deploying a Code 128 font so that we will be ready if we expand the use of barcodes into an application that requires alphabetic characters as well. Scanner set up is pretty easy now, with most being able to plug and play with your computer hardware and able to be configured by scanning barcodes. They function similarly to keyboards, in that if you are some sort of input area (eg. a field or even the body of a mail message), the scanner will feed the data from the barcode into the field as though you typed it on a keyboard.
The vendor we used for the font also supplies code for many different applications, including LotusScript. However, to get the number I encoded to print as part of the barcode, I had to take their VBA script and modify it for LotusScript because their LotusScript code does not support human-readable codes. It was a pretty simple code change though. I configured the scanner to append a tab to each scan that it completed (explanation of why is below). I just plugged it in and then scanned seven codes on a sheet that came with the scanner and it was ready to go.
The Application
The processing form is displayed as a dialog box that allows the user to set some processing options before the input field is displayed. This is done with a programmable table with the options on one tab and the processing actions on a second tab, with buttons to toggle between the two tabs. This helps to ensure the cursor is in the input field when ready to scan by making it the only editable field visible.
The form has the main input field (TrackingNum) and a second, unmarked input field (ActionField) on it. By unmarked, I mean that it is a simple text field that has the Show Field Delimiters option unchecked. The scanner sends the decoded data from the barcode plus a tab character. The tab moves the cursor from the TrackingNum field to the ActionField field. The processing code is in the OnBlur event of the TrackingNum field; that code takes the number from the barcode, finds the document and updates it as necessary. The ActionField field has code in the OnFocus event to move the cursor back to the TrackingNum field, which makes the dialog ready for the next scan.
The form needed to be functional manually as well as with the scanner so the TrackingNum field is defined as a combobox and the form has a button on it that, when clicked, moves the cursor out of the TrackingNum field. The combobox lists items that are available for the user to process and can also be used to type in any number manually. The button just puts the cursor into the ActionField field, which then moves the cursor back to the TrackingNum field.
This is what the dialog looks like. Processing Messages is a reverse-chronological list of the actions taken each time an entry in the TrackingNum field is processed. I couldn’t find a lot about barcoding in Notes, and found nothing about how to actually code an application to interact with a scanner, but I did receive a hints from a couple of Lotus peeps that really helped. In the end, it was a lot easier than I expected.
Technorati: Show-n-Tell Thursday
