Posts Tagged With: Stack Overflow

Proud to be an #IBMChampion

In one of the many jobs that I held, I remember someone telling me that they weren’t sharing knowledge of something because being the only person who knew that gave them job security. I’ve always felt that was foolish. If you’re the only person who knows something, you get stuck doing that same stupid, repetitive task whenever it needs doing. I can think of nothing more boring for a developer than hoarding knowledge. I don’t ever want to have to do the same thing twice – unless doing it the second time is showing someone else how to do it or demonstrating the way things “used to get done” before I automated the task.

So, it’s no surprise that I’ve always wanted to share whatever knowledge I’ve acquired. Sometimes, the knowledge I’ve got to share is so esoteric that people’s eyes glaze over and they ask me to stop talking. (Like the time I was about to talk for 30 minutes about Miljenko Grgich’s life story and how that related to the wine I’d poured, when my best man, Stu Shelton intervened…) Fortunately, in the IBM ICS community, among committed “Lotus” Notes professionals, any short or long blog post, knowledge sharing session or roundtables veritably demands the sharing of esoterica.

Now, here’s the thing. It’s not only encouraged – it is REWARDED.

I really wasn’t good at sharing outside my own work groups until about 2013. I was at Lotusphere (whatever it was called that year) and Marky Roden saw me singing karaoke. As horrid a singer as I am, I believe his thinking had to be ‘anyone who can risk that kind of humiliation can be a conference speaker’. He’d seen me answering questions on Stack Overflow (I’d gotten over a few hundred reputation points and he was shocked that he didn’t know who I was – no one did!) So, he told me I needed to become a speaker.

I’ve worked on that, but not made the breakthrough to the big time. I’ve blogged over the years, helped out with the DC Lotus Users Group (presented once!), participated in more IBM calls/sessions and gotten very involved in MWLUG. I even was on a panel at Connect 2017, talking to executives about technology trends. Then, when Richard Moy asked, I jumped at the chance to be the local host for MWLUG 2017, here in Alexandria, VA.

Nonetheless, I still have felt like I wasn’t doing all that much. I was living with The Imposter Syndrome. So, when time for IBM Champion nominations came round, I would nominate a few people and then cringe at the idea of nominating myself. I’d been nominated by others, but couldn’t get myself to fill out the paperwork. Howard Greenberg was the first to push me toward this. In 2016, Kathy Brown nominated me and pushed me to nominate myself. I wilted when looking at the paperwork, even though it was something I really wanted.

This year, I finally filled out the paperwork, to go with nominations from a few community members and, to my shock, I didn’t need to write a “it was an honor to be nominated” speech while talking about the great stable of Champions out there. I’m swollen with pride. I was named an IBM Champion.

I’ll get some swag. I’ll get a chance to participate more fully in our community. I’ll have a chance for some recognition at the various conferences. I’m excited about it and vowing to put even more effort into our community.

I want to thank everyone who has encouraged me along the way and those who played a particular role in this process. I hope that I can be your humble and obedient servant, sharing knowledge and helping build our community.

Advertisement
Categories: Conferences, IBM Champion | Tags: , , , , , , , , , , | 2 Comments

Sending an automated mail using Outlook from a Notes agent

One of the greatest hurdles we’ve had in our transition from being a Notes mail shop to an Outlook mail shop remaining a Notes application shop is automated emails. When someone clicks a button to approve a document in a workflow, I want the system to quietly notify the pertinent parties without bothering the user.

Before I arrived, none of our applications did that. Every time someone wanted an email to accompany the request for approval, an email was opened in the Notes client using formula language. The code did assign the recipients and add text with a doclink, but the user had to click ‘Send’ for the message to go onward. This did allow them to add explanatory text and choose to route the message to a different approver if someone was not available, so it did have advantages. However, not every message ought to sent that way and we can keep the user inside our application by popping up dialogs to customize or select different recipients, or… whatever flexibility we’d like to provide.

So, a number of the applications I’ve designed, or modified from the original, send notices via Notes mail, generated quietly in the background by LotusScript. 2016 rolls around and our organization makes a strategic move away from Notes mail and into Outlook. I spent a year thinking there was no way for me to keep those notifications without dancing around about Notes mail files and replication. The other day, I wondered if I could use OLE to create the Outlook messages and send them without the user needed to intervene. Shockingly, it didn’t even take me a day to figure out how to do it.

The hardest part was figuring out that passing in 0 for the item type needed to specifically be an integer, not just a zero. If figured this out when I created a task using 3. Since it understood that 3 was an integer, I just created itemType as an integer and then 0 really was 0. (Got the clue from someone’s commented code on Stack Overflow, of course)

Here’s the list of Outlook items you can create, with links so you can examine the events, properties and methods:

Name Value Description
olAppointmentItem 1 An AppointmentItem object.
olContactItem 2 A ContactItem object.
olDistributionListItem 7 A DistListItem object.
olJournalItem 4 A JournalItem object.
olMailItem 0 A MailItem object.
olNoteItem 5 A NoteItem object.
olPostItem 6 A PostItem object.
olTaskItem 3 A TaskItem object.
%REM
	Agent Send Outlook Mail
	Created Sep 12, 2017 by David Navarre
	Description: This Agent creates and sends an Outlook mail message in the background
%END REM
Option Public
Option Declare

Sub Initialize
	Dim outlook As Variant
	Dim message As Variant
	Dim itemType As Integer
	Dim reason As String

	On Error GoTo errorhandler

	Set outlook = CreateObject("Outlook.Application")
	' 0 is mail item '
	itemType = 0
	Set message = outlook.CreateItem(itemType)
	message.To = "David_Navarre@company.com"
	message.Subject = "This is my test"
	message.HTMLBody = "<b>Body bolded</b> and some not"
	message.Send

exiting:
	Exit Sub
errorhandler:' report all errors in a messagebox '
	reason = "Error #" & CStr (Err) & " (" & Error & ") on line " & CStr (Erl)
	MessageBox reason, 16, "Error"
	Resume exiting
End Sub

The most amusing thing to me is that I got my initial clue from a post made way back in 2006. Sometimes, I feel like I’m starting all over again. I guess that’s a good thing, because learning new stuff was always fun (this was too!) and not as intimidating as it sometimes seems when I think about having to learn something new. It’s been nice to be in the upper branches of the knowledge tree, but the truth is that what always separated me from other folks was not what I already knew, but how fast I could learn something new.

Categories: Old Notes, Utilities | Tags: , , , , | 4 Comments

Exception avoided in FTSearchSorted in #XPages

Regular readers will remember that I’ve been playing around with using FTSearchSorted in order to create Excel spreadsheets from Notes databases in XPages. I’d had that in my sample database for a while, but hadn’t used it our production environment yet. I worried that my experience with crashes was going to be repeated and that it would take forever to integrate it into the design. Fortunately, I got over my concern about crashes (that’s what an admin team is for, right?) since my coding and the versioning all make it seem stable. The integration of the changes (modify one form, one button on a custom control and a few lines in a script library) was relatively quick. So, in the spirit of the 442nd, I decided to “Go For Broke”.

Having pushed the changes to my development environment, I set up a new report with a querystring and a sort column. My brilliant code would soon return a report of all contacts with their city = “Bethesda”, sorted by… Position since that was a column in the view and I was being random. It didn’t work. I played around with the querystring and finally decided to use FTSearch without using sort or my sort column. That worked. So, I tried FTSearchSorted, with the column name in parentheses, so it would definitely be string. Then, a number for the column instead. Then, I decided, I wouldn’t provide a sort column at all. That worked. So it wasn’t the method and it wasn’t the values I was passing to it. It just returned a silly null object every time.

So, I turned to Stack Overflow. Sure enough, someone else had the problem. The answer provided (too short in the Stack Overflow admins’ minds) was from Thomas Adrian, “make sure the view is user sortable”. Aha! I went back to my form, on which I had placed useful help text that I didn’t bother to read:

A column may only be used for sorting if it has been designed to allow “Click on column header to sort” on the Sorting tab of the column properties. The relevant options are Ascending, Descending, and Both. Trying to sort a column in an unsupported direction throws an exception.

I even bolded the important part of the text on my form. I hadn’t read it when I picked my sort column, so it threw an exception every time I used one of those unsorted columns.

Since I’ve demonstrated that even I won’t read my help text, I’ve changed the code on the button that allows you to select which column to use as your sort column so that you can’t choose one that isn’t sorted either ascending or descending. It will only list choices that are click to sort one way or the other (or both).

Forall columns In columnArray
	If ( columns.Isresortascending Or columns.Isresortdescending ) Then
		' add each view name to an array if it is eligible for click to sort
		Redim Preserve headerArray ( count )
		headerArray ( count ) = columns.ItemName
		Print headerArray ( count )
		count = count + 1
	End If
End Forall

I’ve added a clearer explanation to Thomas’ answer, but that short sentence was all the answer I needed. Once my expansion of his answer clears the editors, it will look nicer up there, but since you’ve already read this blog post, you won’t need it, eh?

I’ve updated the sample database, so you can download it to see the rest of how it works if the code sample is not enough. I’ll be at MWLUG starting tomorrow afternoon, so make sure to say “Hello”!

Categories: Server-Side Javascript, Xpages | Tags: , , , , , , , | Leave a comment

Dirty pages and keeping users on them in #XPages

As I’ve mentioned before, sometimes XPages does not react the way your Notes users would expect it to react. In particular, I’ve had issues with ensuring that XPages warns the user when they try navigating away from a page that they’ve edited without first saving it. Such pages are “dirty”, since something has changed on them. Back in November we examined this in my post on Modified flags in XPages.

Unfortunately, sometimes I want to inform the user that they’re leaving a dirty page and other times I don’t. Also, sometimes, I seem to be able to add things that don’t warn the user that they’re leaving a dirty page. I’m sure there is consistency, but I haven’t figured out the rules, so I have added some code that handles these situations.

Simple warning of a dirty page

This was described in that prior post. Set the enableModifiedFlag to true and provide a couple of properties to handle it. This should fire whenever you close the XPage or navigate away via a standard link.

<xp:view xmlns:xp="http://www.ibm.com/xsp/core" enableModifiedFlag="true">
	<xp:this.modifiedControl>
		<![CDATA[#{javascript:if ( true ) {return "saveButton1"}}]]>
	</xp:this.modifiedControl>
	<xp:this.modifiedMessage>
		<![CDATA[#{javascript:"Purchase order modified. Click OK to save, cancel to continue without saving"}]]>
	</xp:this.modifiedMessage>
...
</xp:view>

Unfortunately, I’ve found that sometimes, it doesn’t warn me.

Warning when clicking links

I’ve got some navigation on the left side of my XPages, set up in a lovely dojo accordian with links to different parts of the application. However, not every link is set up in the same way. Some are simple and use the modifiedControl to warn users, like this one:

<xp:link text="Attachments" escape="true" id="link3" value="/att_attachmentManagement_view.xsp">
</xp:link>

Some, however, run some scripts to remove some sessionscope variables when switching pages and I was having challenges getting the server-side Javascript to execute. So, I found a way to do both:

<xp:link text="Payment requests" escape="true" id="paymentLink" target="_self" value="#">
	<xp:eventHandler event="onclick" submit="true" refreshMode="complete">
		<xp:this.action><![CDATA[#{javascript:clearDynamicViewSettings();
context.redirectToPage("/pro_paymentRequest_view.xsp");}]]></xp:this.action>
	</xp:eventHandler>
</xp:link>

The unfortunate thing is that when I’m using the redirect, it ignores the dirtiness of the page. Fortunately, Per Henrik Lausten found a way to deal with it on StackOverflow (thanks to PSolano), and posted it as an XSnippet on OpenNTF.

if (XSP._isDirty()){
  if (confirm ("Are you sure you want to navigate away from this page?" + "\n" + "\n" +
    "This document may contain unsaved changes." + "\n" + "\n" +
    "Press OK to continue, or Cancel to stay on the current page.")) {
    return true;
  } else {
    return false;
  }
} else {
  return true;
}

So, when I added that explicitly as client-side Javascript to each eventhandler, it worked beautifully. Now, being a fan of reusable code, I wanted to put it in a CSJS script library and invoke the function all over the place. My function was as shown above, with a little wrapper:

function isClean() {
	if (XSP._isDirty()){
	    ....
	}
}

Then, I just needed to invoke it. My first attempt looked great to me. Call isClean() and the client side would warn the user, then stop them as appropriate.

<xp:eventHandler event="onclick" submit="true" refreshMode="complete">
	<xp:this.action><![CDATA[#{javascript:clearDynamicViewSettings();
context.redirectToPage("/pro_paymentRequest_view.xsp");}]]></xp:this.action>
	<xp:this.script><![CDATA[isClean();]]></xp:this.script>
</xp:eventHandler>

Ummmm, but that didn’t work. In order to prevent the server-side Javascript from executing, the client-side Javascript needs to RETURN false, not just compute it.

<xp:eventHandler event="onclick" submit="true" refreshMode="complete">
 <xp:this.action><![CDATA[#{javascript:clearDynamicViewSettings();
 context.redirectToPage("/pro_paymentRequest_view.xsp");}]]></xp:this.action>
 <xp:this.script><![CDATA[return isClean();]]></xp:this.script>
</xp:eventHandler>

That’s added bonus knowledge – not only are we learning how to stick to dirty pages, we’ve also learned that if you have the CSJS return a value of false, it will not execute the SSJS. I’m sure you can grasp the opportunities for validation provided there.

What about when you don’t want to warn them?

In our application, the user can select the action to perform from a combobox and then click a button to execute it. (Thanks to Scott Good, Henry Newberry and the folks at Teamwork Solutions!) The challenge here is that when the user changes the value in the combobox, the page is dirty! I mean, no data may have changed other than that combobox, but the XPage still feels dirty. Since some of those actions will result in saving the document and all of them result in navigation away from this page in an expected and managed way, we don’t want it to feel dirty. To dodge the problem, I just add a little CSJS to convince that page that it’s not really dirty.

<xp:this.script><![CDATA[XSP._setDirty(false,"");]]></xp:this.script>

So, then the modifiedControl is not tipped off and we execute the intended commands. Now, I’m pretty sure we don’t run into this problem with standard save buttons, but we’re in an unusual case of taking two steps to execute one action.

Note

YMMV. These private calls to XSP methods may go away and using them requires care to ensure you’ve got the right parameters, or so the XPages Portable Command Guide advises us. Of course, everything we code may call functions that will go away and we always need to get the parameters right. So, I’m gonna use them, though sparingly.

Categories: Client-Side Javascript, Server-Side Javascript, Xpages | Tags: , , , , , , , , , , , , | Leave a comment

Multi-value Sorting in #Java

I’ve been working on trying to figure out how to sorting Notes views on-the-fly as we’re exporting to Excel and my first thought was, “Oh, that will be in the POI documentation.” I assumed that there would be a simple function in the Apache POI documentation for XSSF to sort rows and that it would also allow us to do multiple column sorts, just like we can manually in Excel. Apparently, doing this is not high on the POI team’s list of tasks.

So, after coming up dry on POI, then on Stack Overflow, I decided to check the interwebs to see what I could find. Sorting collections in Java is actually not all that hard. There are built-in functions for it, so all you have to do is issue a Collections.sort(myObject). Where it gets interesting is in how you compare the objects in the collection.

In order to determine order within the collection, all you have to do is create an integer function within the class named compareTo. Then, it will sort them based on the result returned (comparing one object at to another, not jumping six or seven places at once). So, it you want to sort on multiple values, you return them in reverse order of significance. For example, if I want to sort based on Department, then roll number, then name, I would use the following code:

if ( alphaDepartment == 0 ) {
	if ( rolldifference == 0 ) {
		return alphaName;
	}
	return rolldifference;
}
return alphaDepartment;

When the Department does not match, we return the Department order. If Department matches, we check roll number, returning the difference if it does not match. Finally, if both Department and roll number matched, we return the name order. The bulk of the work is in setting up your compareTo. While this one is hard-coded, I’m sure we’ll be able to figure out how to use variables to identify which value we want to use in our own sorting.

(I’ve dispensed with listing the setters, as they are not needed in this example.)
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@SuppressWarnings({ "unchecked" })
public class Student implements Comparable {

	private String name;
	private String rollNumber;
	private String department;

	// Constructor for the class
	public Student(String name,String rollNumber,String department){
		this.name = name;
		this.rollNumber = rollNumber;
		this.department = department;
	}
	public String toString() {
		return name + ", " + rollNumber + ", " + department;
	}
	public String getName() {
		return this.name;
	}
	public String getRollNumber() {
		return rollNumber;
	}
	public String getDepartment() {
		return this.department;
	}

	//This Function is responsible for sorting.
	public int compareTo(Object student1) {
		if (!(student1 instanceof Student))
			throw new ClassCastException("A Student object expected.");

		Student studentInput = (Student) student1;
		String thisname = this.getName();
		String thatname = studentInput.getName();
		int alphaName = thisname.compareTo(thatname);

		int rollNumb = Integer.parseInt(studentInput.getRollNumber());
		int hostObjrollNumb = Integer.parseInt(this.getRollNumber());
		int rolldifference = hostObjrollNumb - rollNumb;

		String thisDepartment = this.getDepartment();
		String thatDepartment = studentInput.getDepartment();
		int alphaDepartment = thisDepartment.compareTo(thatDepartment);

		if ( alphaDepartment == 0 ) {
			if ( rolldifference == 0 ) {
				return alphaName;
			}
			return rolldifference;
		}
		return alphaDepartment;
	}

	public static void main(String[] args) {
		List<Student> studentList = new ArrayList<Student>();

		//Create our Student objects
		Student s1 = new Student("Tom","3","CS");
		Student s2 = new Student("Jerry","1","Electronics");
		Student s3 = new Student("Merry","4","IT");
		Student s4 = new Student("Tom","2","IT");
		Student s5 = new Student("Jerry","5","IT");
		Student s6 = new Student("Merry","6","Electronics");
		Student s7 = new Student("Tom","1","CS");
		Student s8 = new Student("Jerry","2","IT");
		Student s9 = new Student("Merry","3","Electronics");

		//Add Students to our ArrayList

		studentList.add(s1);
		studentList.add(s2);
		studentList.add(s3);
		studentList.add(s4);
		studentList.add(s5);
		studentList.add(s6);
		studentList.add(s7);
		studentList.add(s8);
		studentList.add(s9);

		//The actual sort command
		Collections.sort(studentList);

		System.out.println("\nThe student list in ascending sequence is:\n");
		for (Student person : studentList) {
			System.out.println(person);
		}
	}
}

As you can see, I included the data in the Java code, since I’m trying to keep it simple. When we move forward to trying this with either the Excel sheet we’ve produced or the data before we export it to Excel, things will be more complicated. Nonetheless, I am enthused about the start I’ve made here.

There are few places that were key in helping me understand this AND from which I borrowed much of the code. On Stack Overflow, there was a question about sorting a multi-dimensional array that got me started (I’ve submitted a correction to the OP’s self-answer, since his self-answer doesn’t actually work.) While that taught me some things, none of that code appeared here. The Student object and my first exposure to how compareTo functioned came in a post on sorting a list in ascending order in Java. Other pages had mentioned compareTo, but it made more sense when I saw it there. The final, clear understanding came with sorting a collection containing user-defined objects, which thankfully also made clear to me how to loop the output properly.

Categories: Java, Utilities | Tags: , , , , , , | 2 Comments

Puzzling behaviour of Java class files in design refreshes in #xpages

This is not a post in which I’m able to dispense some knowledge I’ve gained, but, rather, one in which I share my befuddlement by some of the new things I’m dealing with in XPages.

We have a database suite originally designed for us by Scott Good, Henry Newberry and the good folks at Teamwork Solutions. It’s based off their brilliant Process It! workflow Notes solution. In the original design, they used version 1.3 of Mark Leusink’s Debug Toolbar. Since version 3.01 is now available on OpenNTF, I decided to upgrade. One of the advantages of the upgrade is that now the code resides in Code/Java, accessible more directly from the Designer client than it was when it had to be stored in WebContent/WEB-INF/src. For me, this is far more convenient, since I can see more properties directly (especially about prohibiting design refresh).

Nonetheless, I seem to be having troubles.

I updated the design, then did a clean and a build on my template. Then, I refreshed design on the server copy from the template. At first, it seemed like it wouldn’t bring the built .class files over to the server copy from my template. Now, when I do a design refresh, it “horks” the five files, reducing them all to -1 bytes. If I do a clean & build on the server copy, the files get their expected size.

I believe my classpath is correct, as it builds them properly when I tell it to build in either the template or on the server.

<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="Local"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="output" path="WebContent/WEB-INF/classes"/>
<classpathentry kind="src" path="Code/Java"/>
</classpath>

In the ACL, this ID has Manager rights, including for creating LotusScript and Java agents. I’ve signed all the files in Code/Java with this ID. I build it with this ID. I conduct the design refresh with the same ID and those same rights.

Does anyone have any ideas what I might be doing wrong that’s inhibiting the class files from refreshing with the rest of the design?

There are two Stack Overflow questions that deal with this directly:

Class files not being created after refresh from template?

XPages Java corruption issue

One of my co-workers did open the design to check the binding for one field, which must have corrupted the files once. That doesn’t explain all the instances, but it helps. This straddles the line between a reason to use templates (so no one even glances at the design in production) and reason not to use templates (because it’s forced me to do builds on production since template changes only produce corrupt class files). Interestingly, it’s only the Debug Toolbar class files that have the problem – the Teamwork Solution ones haven’t corrupted.

Update: It gets worse. I tried building on the local replica so that I could do it quickly, then replicate the class files up to the server. No luck. It overwrites the local files with the corrupted ones from the server.

Categories: IDE, Java, Utilities, Xpages | Tags: , , , , , , | 3 Comments

Exploring requestScope, viewScope and multiple source documents in #xpages

As I’ve mentioned previously, much of my current work revolves around one database used to allow organizations to request grants for one of our projects. In doing so, when they open the XPage to request a grant, there are four separate attachment documents as source documents to the XPage, as well as the main document and a contact document. One of the issue I had with our initial roll out of the database was that users uploading their attachments ended up creating many rep/save conflicts and getting every attachments loaded on every source document except the contact document. I had no idea why, but it was in production already and with less than 100 grant requests, I simply manually fixed all the rep/saves as they created them. Basically, using a hammer to repair ceramics.

The problematic code was, unbeknownst to me, my source declarations.

<xp:this.data>
	<xp:dominoDocument var="workplanDoc" formName="AttachAnnex" />
	<xp:dominoDocument var="projectBudgetDoc" formName="AttachAnnex" />
	<xp:dominoDocument var="budgetAssumptionsDoc"  formName="AttachAnnex" />
	<xp:dominoDocument var="performanceIndicatorsDoc" formName="AttachAnnex" />
</xp:this.data>

I knew something was wrong. I’d originally had those four source documents declared at the top of the XPage, but when I moved them onto a custom control, it seemed to me that I’d resolved them problem. Unfortunately, I think it was only working some of the time (new grant requests, which had no documentId= in the URL, I suspect), if at all.

So, I wondered and I searched. Then, the daily compilation of Lotus Notes questions on Stack Overflow came through. The first item was labelled “XPage xe:Dialog box is edit previous create document”, which didn’t seem too interesting, but the snippet from the question raised my eyebrows, “I have a custom control that create a new document through ext-lib dialog box that work fine. However when the action is performed the second time it edit the document instead of creating a new …” I realized he was dealing with multiple data sources and having problems similar to mine. So, I opened the question and found great advice from Patrick Sawyer.

set ignoreRequestParams to true and then set the scope to request

Then, I followed links to his post to get more information, finding that even though I’d read Patrick’s question about multiple data sources and commented on it, no less, that I had no recollection of that solution (provided by Tim Tripcony!) So, I changed my code.

<xp:this.data>
	<xp:dominoDocument var="workplanDoc" formName="AttachAnnex"
	 ignoreRequestParams="true" scope="view" />
	<xp:dominoDocument var="projectBudgetDoc" formName="AttachAnnex"
	 ignoreRequestParams="true" scope="view" />
	<xp:dominoDocument var="budgetAssumptionsDoc" formName="AttachAnnex"
	 ignoreRequestParams="true" scope="view" />
	<xp:dominoDocument var="performanceIndicatorsDoc" formName="AttachAnnex"
	 ignoreRequestParams="true" scope="view" />
</xp:this.data>

Tim explained the function of ignoreRequestParameters well in his answer to Patrick’s question:

If you omit the ignoreRequestParams attribute, then this data source doesn’t ignore the URL request parameters. Instead, it looks for parameters named databaseName, formName, documentId, and action. If any of these parameters are included in the URL, the value of each overrides what is defined on the data source.

So, what was happening was that if you started a grant request via the simple link, which was just the XPage URL, ending with “Application.XSP” then there were no parameters to worry about and the source documents might be created properly. However, if the users followed the link I’d emailed them, pointing to their specific document, with action=editDocument, and documentId= the UNID of their grant request, it would use those values and upload the attachment to main document, modify the form name to “AttachAnnex” and, with multiple uploads of many attachments, create a flurry of save conflicts. I’d go through and change form names on one of them and save the others, so they all got assigned new UNIDs.

Now, I tried both requestScope and viewScope, finally settling on viewScope. If I use requestScope, then each attachment upload creates a new Notes document with the attachment on it. If I use viewScope, then, for the span of that user session, each upload that the user does to that source document goes onto the same Notes document. If they come back and upload more documents in a separate session, it should create a new Notes document that would hold all of the attachments uploaded for that source document. So, with requestScope, if the user had 10 files to upload, some for each source document, they would be creating 10 Notes documents, regardless of whether they uploaded them in one sitting or several. With viewScope, if they uploaded 3 files to workplanDoc in one sitting, all 3 files would end up on 1 Notes document. Since I’d already worked out how to handle the linking to attachments for multi-attachment Notes documents, I chose viewScope, which keeps the attachments more organized.

Now, if I were to experiment, I expect that using sessionScope would create problems for any user who submits multiple grant requests, as it would use a four source documents for their entire session. This would put every workplan attachment onto one workplan Notes document that would end up associated only with the last grant request they edited. I could be even more foolish and grant the source documents applicationScope, which would put every attachment that any use uploaded into one of the four application-wide source documents, each time, associating it with a different grant request. Nightmarish!

I’d been wondering, and heard multiple speakers who discussed scope at various conferences say, “I’m not real sure why you’d use requestScope, but I know there are reasons.” Well, creating new source documents from an XPage that uses multiple source documents is just such a reason.

Categories: Xpages | Tags: , , , , , , | 1 Comment

Using XPage ACLs to limit access

Among the items I’d been working on for the presentation at the DCLUG earlier this month was an evaluator’s page.

Now before I get into the code snippet, let me lay out a bit more of the design concept to put this in context. Users would anonymously create grant requests via browser, then internal users would assign evaluators with their Notes clients, and those evaluators would login via browser to fill out their assessments of the requests. So, I needed to come up with a way to force the logins and inhibit anyone who wasn’t authorized from seeing the evaluators work lists or assessments.

Notes does this wonderfully well, but as I’m breaking new ground with XPages, I had to search around for clues on how to do it. After a while, I did finally find something on my go-to source, Stack Overflow. (Thanks to Matt White and AndrewGra!)

<xp:this.acl>
    <xp:acl>
       <xp:this.entries>
          <xp:aclEntry type="ANONYMOUS" right="READER"></xp:aclEntry>
          <xp:aclEntry type="DEFAULT" right="EDITOR"></xp:aclEntry>
       </xp:this.entries>
    </xp:acl>
 </xp:this.acl>

While this is nice, it also opened me to wondering about all the other options of the aclEntry control. There are 5 parameters to be considered. The two required parameters are intriguing.

type – string – Defines type of entry, valid values are USER, GROUP, ROLE, ORGUNIT, ORGROLE, DEFAULT, ANONYMOUS

While both ORGUNIT and ORGROLE are deprecated, the other 5 are quite familiar. If you set type to either DEFAULT or ANONYMOUS, you don’t supply any of the name values, since these are effectively “unnamed” access types. For USER, GROUP and ROLE, you must supply name values.

right – string – Defines rights for this entry, valid values are NOACCESS, READER, EDITOR

On an XPage, you really are only concerned about end-user access, so it’s either none, reading or editing. Distinguishing between Author and Editor depends on the ACL and whether their are Author names fields on the source documents, so not something one wants the XPage monkeying around in anyway.

Since neither DEFAULT nor ANONYMOUS requires any name information, the next two parameters are optional.

fullName – string – Defines users full name e.g. John Smith

name – string – Defines entry name

There’s a real lack of clarity here as to what goes where, in particular, what to do about ROLES. Is it a name or a fullName. Do you use brackets [ ] or just the text? So, I fiddled around. I’d almost figured out how to implement ROLES myself when I searched just a bit more and found Russ Maher’s guidance. Basically, put brackets around the name value and straight text for the fullName. Here’s my ACL to limit the XPage to only those users with the Evaluator role.

<xp:this.acl>
	<xp:acl>
		<xp:this.entries>
			<xp:aclEntry type="ANONYMOUS" right="NOACCESS"></xp:aclEntry>
			<xp:aclEntry type="DEFAULT" right="NOACCESS"></xp:aclEntry>
			<xp:aclEntry fullName="Evaluator" right="EDITOR" type="ROLE" name="[Evaluator]">
			</xp:aclEntry>
		</xp:this.entries>
	</xp:acl>
</xp:this.acl>

The final parameter is also optional.

loaded – boolean – Specifies whether or not the tag instance should be created when the page is loading. Value defaults to ‘true’.

This one is obviously for people far more clever than I. I suppose that if you want the ACL to only take effect after some other event that trips the loaded flag to true or something. I’m not real sure. It is inherited from com.ibm.xsp.BaseComplexType if that helps you figure it out.

Just as form access and creation controls are very sneaky, these are as well. Since the properties don’t show up in the nice little properties box for the XPage and you have to hunt into either the source or the All Properties tab (under data), they are sure to create some trouble-shooting issues, especially when you’re new to XPages. So, I’d recommend you make sure to put these at the top of your XPages and I would be careful about using them on custom controls, unless you do everything in your custom controls, simply because of the lack of visibility.

Hope that helps someone looking for help with security in their XPages. I’m pretty happy with how easy it turned out to be to code it.

Categories: Security, Xpages | Tags: , , , , , , , , , | 4 Comments

Repeats and dynamic field bindings #xpages

In our purchase order module, we need to ask certain questions depending on the source selection method, competition type and total cost of the PO. These questions are likely to change over time and in between different instances of the database. So I have a view containing the questions, so that I can add questions dynamically to my XPage without needing to change the code.

To be honest, doing this in a regular Notes form would have required either putting every question on the form or putting a bunch of fields on the form for all the answer fields. Either method would be sloppy and wasteful. There could have been dozens of unnecessary answer fields hidden by hide-whens and constant revisions between various instances of the database leading to a configuration management/version control nightmare.

Doing it in a repeat control, with the source view filtered by values passed in an array, I can get dynamically selected questions with only the necessary fields being stored on the document.

In order to do this, I had to use all of my minimal XPages knowledge, ask several questions on Stack Overflow, re-read portions of Mastering Xpages and scour the help documentation. You, however, can simply follow my guidance and have this solution for your own.

The Question FormCompetition Question Form

I created a relatively simple Notes form, based on the ones supplied to us for keywords by Teamwork Solutions. The layout is clean and very readable, plus re-using it allows more consistency within our application. A number of the fields are used to determine when and where the question will display (Category, Competition type, Source selection method and Dollar ranges). The checkbox values are derived from keyword documents, to allow maximum flexibility. I’m allowing five field types for answers: Yes/No, Dialog Box, Text Box, Date and Date Range. I had tried Rich Text, but was having problems with losing the handle to the document when saving, and since it wasn’t really required in this implementation, dropped that for later review. Sometimes, the question has a related question to extract more details, so I allowed two questions to be placed on one question document to ensure they display together. Down at the bottom, I’ve got the fields to which the answer fields will be bound on the Notes document. One hazard to allowing the field to be named in this configuration document is that there might be duplicate use of a field name, by the risk level strikes me as low.

Setting up the Repeat

The way that I started to understand repeats was actually when I set up jQuery mobile access to some data after viewing Richard Sharpe’s webinar for TLCC on Building Your First Mobile Application Using XPages I was able to take the knowledge I acquired doing that and use it to link from one Requisition to many Purchase Orders as demonstrated on this blog.

The repeat itself is not that interesting, but, one of the technical tricks in the setup is. As I’ve started to learn XPages, I’ve gotten comfortable with variables stored in applicationScope (available to everyone), sessionScope (available throughout the current user’s session) and viewScope (available on the current page), but had no idea when one would ever use requestScope variables. It turns out that they can be very useful to us here, though we’re going to access them using a dataContext rather than labelling them as requestScope variables.

Since I want to take a value from each Question document to determine the field to bind to on the Purchase Order document, the syntax and re-use will be far easier if I can establish a variable for that field name used within each row of the repeat. This way, we can use Expression Language to refer to the field on the Purchase Order document, binding to it dynamically.

<xp:repeat id="repeat2" rows="30" var="rowData" style="width:700px" value="#{competitionQuestionView}">
    <xp:panel>
        <xp:this.dataContexts>
            <xp:dataContext var="fieldName">
                <xp:this.value><![CDATA[#{javascript:rowData.getColumnValue ("FieldName");}]]></xp:this.value>
            </xp:dataContext>
        </xp:this.dataContexts>
        <--- insert code here --->
    </xp:panel>
</xp:repeat>

First, we want to display our question, with the layout handled by a table. We just grab the column value and display it.

<xp:table>
    <xp:tr>
       <xp:td style="width:200.0px;">
          <xp:label id="label1">
             <xp:this.value><![CDATA[#{javascript:rowData.getColumnValue("Question");}]]></xp:this.value>
          </xp:label>
       </xp:td>

Then, in the right-hand cell, we have the various possible answer fields. Note that since only one is rendered (the rendering formulas being mutually exclusive), it doesn’t matter that we bind to the field multiple times, since only one binding actually appears on the page. Tim Tripcony guided me to this solution in his answer on Stack Overflow. By using array syntax, we bind to the field on the poDoc using the value of our requestScope variable, fieldName. Remember, as a requestScope variable, it is only “alive” for the duration of this data retrieval to create the field binding. The binding formula ends up being very simple: #{poDoc[fieldName]}

Here’s a portion of the answer fields to give you the flavor…

<xp:td>
   <xp:inputText id="inputText1" style="padding-top:2px;width:400px">
      <xp:this.rendered><![CDATA[#{javascript:rowData.getColumnValue("FieldType") == "Text Box"; }]]></xp:this.rendered>
      <xp:this.value><![CDATA[#{poDoc[fieldName]}]]></xp:this.value>
   </xp:inputText>
   <xp:comboBox id="comboBox1" style="padding-top:2px;">
      <xp:this.rendered><![CDATA[#{javascript:rowData.getColumnValue("FieldType") == "Dialog Box"; }]]></xp:this.rendered>
      <xp:selectItems>
         <xp:this.value><![CDATA[#{javascript:rowData.getColumnValue("DialogChoices")}]]></xp:this.value>
      </xp:selectItems>
      <xp:this.value><![CDATA[#{poDoc[fieldName]}]]></xp:this.value>
   </xp:comboBox>
   <xp:inputText id="inputText2" style="padding-top:2px;text-align:left">
      <xp:this.rendered><![CDATA[#{javascript:rowData.getColumnValue("FieldType") == "Date"; }]]></xp:this.rendered>
      <xp:dateTimeHelper id="dateTimeHelper1"></xp:dateTimeHelper>
      <xp:this.converter>
         <xp:convertDateTime type="date"></xp:convertDateTime>
      </xp:this.converter>
      <xp:this.value><![CDATA[#{poDoc[fieldName]}]]></xp:this.value>
   </xp:inputText>
   <xp:radioGroup id="radioGroup1" style="padding-top:2px;width:86.0px">
      <xp:this.rendered><![CDATA[#{javascript:rowData.getColumnValue("FieldType") == "Yes/No"; }]]></xp:this.rendered>
      <xp:selectItem itemLabel="Yes" itemValue="Yes"></xp:selectItem>
      <xp:selectItem itemLabel="No" itemValue="No"></xp:selectItem>
      <xp:this.value><![CDATA[#{poDoc[fieldName]}]]></xp:this.value>
   </xp:radioGroup>
</xp:td>

Since my Question document also allows for the addition of a comment question after the non-TextBox answers, I have another line on the XPage to display those (Note that the dataContext for the commentFieldName requestScope variable has to be added to that panel inside the repeat control as well).

<xp:tr id="commentRow">
   <xp:this.rendered><![CDATA[#{javascript:rowData.getColumnValue("DisplayComment") == "Yes"; }]]></xp:this.rendered>
   <xp:td style="border-bottom:grey solid 0px;padding-top:2px;">
      <xp:label id="label3">
         <xp:this.value><![CDATA[#{javascript:rowData.getColumnValue("CommentLabel");}]]></xp:this.value>
      </xp:label>
   </xp:td>
   <xp:td id="commentCell" style="border-bottom:grey solid 0px;padding-top:2px;">
      <xp:inputText id="inputText3">
         <xp:this.value><![CDATA[#{poDoc[commentFieldName]}]]></xp:this.value>
      </xp:inputText>
   </xp:td>
</xp:tr>

In my next blog post, we’ll look at how we filtered the view data source in order to only display the questions we want on a particular purchase order.

If you don’t already look at XPages questions on Stack Overflow, you really ought to, and if you haven’t been keeping up with TLCC’s webinars, check out the series.

Categories: Server-Side Javascript, Xpages | Tags: , , , , , , , | 1 Comment

Blog at WordPress.com.

%d bloggers like this: