Friday, August 2, 2013

A Great Little League Season!

For the last four years, I have coached our daughter's softball team.  Well, it actually started two years before that in tee ball, but that wasn't "softball".  Two years ago, I was a coach for the 7-8 year old team that played in a local district tournament.  That taught me a lot about coaching and how to play to win games.  Last year, I was a coach for the 9-10 team and I learned even more, though we had a young team and didn't win a game.

This year was different.  Half the team was girls that had played on last year's 9-10 team and most of the others played on last year's 7-8 team that finished second in their tournament.  We had experience, and it showed.  Our first game in the District tournament was against an opponent (West Portage) who traditionally fields tough teams and the game went back and forth until we finally took the lead in the top of the 6th.  We brought in our best pitcher to close the game, and that she did.  I even got a little choked up after the game when I told the girls how proud I was of them (and they reminded me of that after every game we played).  Our second game was against a league (Parchment) that our league had traditionally had trouble beating but we were able to win 7-0.  Our third game was against a team (Gull Lake) that had beaten their other opponents pretty handily and, because they had been winning as well, were able to use their best pitchers.  We beat them too!

That brought us to the championship game against West Portage.  They had gotten back to us by winning 4 games in 5 days - in 90+ degree heat.  It was #1 pitcher against #1 pitcher, but we had the advantage of being rested.  It was close for a while but our girls poured it on as the other team tired and we won 13-2 in 5.  It took a minute to realize that we were District 2 Champions!  After running around the field with the banner and taking lots of pictures, we got our shirts and our pins and lots of information about playing in the state tournament in Escanaba.  Yes, Escanaba - 8 hours away.  Some of the families had to make big sacrifices to make it happen, but all 12 girls were able to go for at least part of the tournament.  Amazing!  We had a couple more practices for fine tuning and focus, and then we were off to the Upper Peninsula.  And the adventure part of the story began.

We ended up being in the UP from Thursday through Tuesday morning.  We were scheduled for pool play from Friday through Sunday, followed by a single elimination tournament for the top 8 teams.  Thursday night there were tornado warnings so our initial coaches meeting was cut short and there were no games Friday because it rained CONSTANTLY.  Like nearly biblical amounts of rain.  Every building we went into had buckets somewhere to catch leaks.  We played Friday's schedule on Saturday and beat Tecumseh 4-2, which was really exciting.  They were a tough team and looked as though they had played together for a while.  We got approval to play doubleheaders on Sunday (Saturday and Sunday's scheduled games) so we could get back on schedule but it rained all day Sunday too!  Then, so that we could get everyone at least 2 games and make it a legitimate tournament without keeping everyone there for 10 days, the coaches agreed to change to a 16-team single elimination bracket seeded by the results of Saturday's games.

We played a team (Clare) who didn't have their #1 pitcher on Saturday because she was sick so they were seeded lower than they probably should have been.  We got down by 5 in the first and took a while for our bats to wake up.  We almost came back in the 6th but lost 7-5.  It was a good game where each team got nice hits and made good defensive plays.  We had stayed at the same hotel as Clare and some of the girls had gotten to know each other a little bit so that made it fun.  Plus, they were just nice people.

We had a lot of "down time" because of the rain but got to do some fun things together and a little bit of sightseeing up there.  We all went bowling Friday night and almost everyone went out to Rapid River Falls (it was really more of a "rapids" than a "falls") on Monday morning.  We practiced in a nice park in the drizzle, had a couple of "team dinners", including a final one in the park where we had practiced, and spent a lot of time in the hotel pool.  We said goodbye to one team member who had to go home early and made arrangements so two others could stay when their families needed to go home.  We took care of each other.

I'm proud that we are (I think) the first Westwood 9-10 softball team to win the District.  I'm also proud that we were the only Westwood team to win the District this year.  These girls represented Westwood so well!  If we had played the original schedule, we'd have gotten at least 3 games (and probably 4, based on our pool).  But we were 4-0 in the district and 1-1 in the state and can always say we're District Champions - we have the banner to prove it. :)  And it was a fun ride overall.  I hope this wasn't a "once in a lifetime experience", but if it was, it was worth every minute.

Saturday, May 18, 2013

Building a Softball Bat Rack from PVC Pipe

Our dugouts have been quite cluttered this year with bats hanging through the chain link fence and falling to the ground, or just laying on the ground in the first place.  I've seen bat holders made from PVC in other places but couldn't find a pattern or instructions to create one so I just figured it out on my own.  I thought I'd document it here so I could refer back to it and so others might be able to benefit.

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 read this post from Brad Balassaitis and shared that "I should have blogged that last year when I did it".  A couple of people encouraged me to blog it just for a different perspective, so here we go.

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();
sessionScope.put("bundleProcReturn",@RightBack(curURL,"/"));
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. :)

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

Recently I had a request to add functionality to an XPages application I wrote and implemented last year.  They wanted the ability to log changes made in specific fields.  I did some searching and found the resources I needed:

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 changed
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();
      
Map viewScope = ExtLibUtil.getViewScope();
HashMap oldValuesMap = new HashMap();
HashMap newValuesMap = 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);
Code to read the old and new values and write them to a log field named ActivityHistory:
var dNow = @Now();
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");
    }
}
Updated addToList code (I used 'first' and 'last' instead of 'prepend' and 'append'):
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

Just a quick, rather meaningless post, to say that I changed to a new design on the site.  I thought it was time to update to something with a more "current" design.  Let me know if you run into any issues using it.

Tuesday, January 24, 2012

More Excel Code: Inserting and Copying Rows

I was working on some code to export data to a pre-formatted Excel file and, as part of that, needed to be able to insert rows into a range so that the data I added would be included in formulas below it (they were column totals and data summaries). As I find typical when trying to control Excel from LotusScript, I couldn't locate examples of the code I needed. That doesn't mean they aren't out there, just that I couldn't find them.

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



This LotusScript was converted to HTML using the ls2html routine,
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

I had a problem last week with a couple of forms that have fields that inherit values from fields on the document that is open when the new document is created. On one form, the field values were set properly, but on the other they were not. The form being created is a response-to-response form. The form that worked was a response form, so at first I thought the problem with the form that was not working was because it was a main form. I discovered that wasn't the problem when I was able to get that form to inherit a text field.

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.