Tuesday, August 07, 2007

Using an Embedded View as a Picklist

[Hi Everybody...Chris here. I'm very happy to be able to present to you the first guest post on Interface Matters. I thought this was a cool technique and since it was demonstrated to me using the Kwik-E-Mart database, how could I refuse! ;-) So, please welcome Keil Wilson with a great post on using an embedded view as a picklist. Take it away, Keil...]

The problem…
I’ve built dozens of applications that included Picklists (either @Picklist or NotesUIWorkspace.Picklist) to allow the users to select one or more documents from a list of documents. Generally, this Picklist is called from a button on a form that tests if the user pressed ‘OK’ or ‘Cancel’ and then performs some action using the selected document(s). The Picklist function in Notes is very cool for a number of reasons, but one of the biggest is that it just uses a Notes View to display a list of documents from which the user can pick. This gives you all of the formatting controls available to Notes Views and can help provide a consistent user experience so that the list of documents displayed in the Picklist looks just like the lists of documents displayed in your Notes Views. You can include the view icons or graphics, categories, and you can filter that list of documents displayed using view categories.

What if your users want to select documents from one of two or three different categories, though? What if, for instance, the users want the Picklist to show all accounts sometimes, current accounts sometimes, and past accounts sometimes? You have three different buttons on your form, right? If the user presses the wrong button, then they have to push Cancel to close that Picklist and then click the correct button. What if you could just have one Picklist that can display all three categories depending on which radio button or combo box selection the user chooses? We all work with similar dialogs on a regular basis, like the “Choose address book” combo box on the Select Addresses dialog in the mail file or the “Server” selection combo box in the open database dialog.

This was my challenge recently, and while the solution I developed wasn’t without compromise, I think it gets pretty close to a Picklist with multiple options for the documents displayed in the list.

About this example…
This review will use Chris’ Kwik-E-Mart Squishee Order System as an example (get the database now). The challenge here was to add an action on to the “Kwik-E-Mart Squishee Order Form” form that would allow the user to copy an existing Squishee order item. To do this, the user would select the existing item from a Picklist like control that could be filtered to show only those Squishee order items on the current order form, or show all Squishee order items in the whole system.

  

Check out the video demo of this in action...

The above dialog box is triggered from a simple action hotspot on the “Kwik-E-Mart Squishee Order Form.” There are six design elements involved in the technique I used here. There are two embedded views (Current Customers & All Customers), two shared actions (OK & Cancel), the “Copy an Order” dialog box (on which the two views are embedded in a programmable table), and the modified “Kwik-E-Mart Squishee Order Form” originally created by Chris (the only change was the addition of the “Copy Order” action hotspot). The key concepts here are: 1) a dialog box with a two row programmable table that displays each embedded view based on a radio button selection, 2) the use of the action button bar on the embedded views to create/display the “OK” and “Cancel” buttons, and 3) storing of the selected document’s NoteID in the Notes.ini file for later use after the dialog box has gone out of scope. All of these are simple concepts (often discussed before by Chris and other developers), but they come together to make a flexible and functional solution.

So let’s start with the views…
In the Kwik-E-Mart Squishee Order System, there was an existing view that did almost exactly what I wanted. So I did what any good coder does…I copied it, modified it slightly, and am now taking credit for its form and function. In fact, I did that twice. So the embedded view on Chris’ “Kwik-E-Mart Squishee Order From” has been adapted to work in the “Copy an Order” dialog box. It is important to note here, that because I’m working with essentially the same view that Chris had already taken the time to create, test, and format beautifully, not only did that lighten my work load, but it also provides a pretty consistent look and feel on my new dialog when compared to the existing system. Once I had modified the views’ selection formulas and columns to meet my needs, I cleared all of the action buttons from the view. Now all I needed were my two new action buttons.

Actions are the key…
The view action buttons are the key to this whole solution, as well as the weakest point. In order to determine which document(s) were selected in the embedded view, I needed a way to trigger some event within the embedded view. I tried several different things, like Queryclose (which doesn’t fire in an embedded view), but nothing worked. Finally, after some research on the developerWorks forums (and reading posts like this one from Jamie Grant), I decided to try replacing the dialog box form’s “OK” and “Cancel” buttons with view action buttons. Notes gives developers a lot of control of the look of view action buttons, but their placement above the view is pretty much locked down. It is this unorthodox placement for the “OK” and “Cancel” buttons that gave me the biggest pause about this approach.

Because both embedded views display the same type of documents and I wanted to do the same thing with the selected document regardless of which view was displayed, I was able to create two shared actions (one for “OK” and one for “Cancel”) that would go on both views. I had to do a lot of tweaking on the format of the view action bar for each view. If you’ve never tweaked the action bar properties (accessing these properties is less than intuitive), you can right-click in the Action Pane in Domino Designer and select “Action Bar Properties…”, or you can click anywhere in the Action Pane and select “Action Bar” in the properties box list. Use the Notes help and take some time exploring all the action bar properties options and what they do. I’ll explain what the buttons do, but you can download the sample database to see the code.

The code behind these action buttons is very simple. The OK button does four things:
1. Get a handle to the parent form/document currently open in the UI (our dialog box).
2. Get a handle to the document that is currently selected in the embedded view.
3. Copy the NoteID of the selected document to an environment variable in the Notes.ini.
4. Close the dialog box using the UI handle we got earlier.

The “Cancel” button does three things:
1. Get a handle to the parent form/document currently open in the UI (our dialog box).
2. Clear any value in the Notes.ini variable (this indicates the user pressed “Cancel”).
3. Close the dialog box using the UI handle we got earlier.

Using the environment…

Back on the “Kwik-E-Mart Squishee Order From” our “Copy An Order” button has called our dialog box and is waiting for the user to select their Squishee order item to copy. Then the user presses “OK” (or “Cancel”), the dialog box closes and control comes back to the “Copy An Order” code. This code does the following three things (again, download the database to see actual code):
1. Test the return from the dialog box to see if the user selected something. This is done by checking if the environment variable we set earlier has a valid NoteID in it. If there’s nothing but an empty string (“”), then the user pressed “Cancel.” If there’s anything but an empty string or a valid NoteID in that variable, then we’ve got an error.
2. Assuming the user has pressed “OK”, we then create a new copy of the selected document and associate it with the current “Kwik-E-Mart Squishee Order Form.”
3. Finally, we refresh the embedded view and the current UI document. This causes the new document to be displayed in the embedded view.

And Then?!
So how could I have done this better? I was a bit uncomfortable about writing/reading stuff in the Notes.ini file (maybe that’s just a hold over from my R4 past), but everything seems to be pretty stable. The other major concern I had was about the location of the “OK” and “Cancel” buttons. Does anyone find the non-standard layout of the buttons unappealing or problematic? I tested this with the users and no one commented on it at all. Is it possible that button location is a bigger deal to me than it is to my users? I’m interested to hear how others could (or already have) improved on this approach. Thanks for reading.


About The Author: I've been a Notes/Domino developer and consultant in Nebraska for about 10 years. I've spent the last several years working on a Notes Client focused development project for a government agency in Nebraska. The scope of this project requires innovative solutions to typical Notes Client limitations (one of the reasons I love Interface Matters). In my past lives I spent time at several small companies managing networks, manning help desks, implementing Crystal Reports/Crystal Enterprise solutions, developing and administering training, and writing code in Notes, VB6, SQL, Java, and C#.


21 comments:

Nathan T. Freeman said...

A few tweaks...

1) You can arguably make the OK/Cancel actions seem more consistent with other apps if you right-align them.

2) You need to write to an environment variable. Without getting to as elaborate a solution as The Revolution, from inside the dialog box, calling NotesUIWorkspace.CurrentDocument gives you the ui doc of the *ORIGINAL DOCUMENT FROM WHICH YOU CALLED THE DIALOG.*

Since you're inside the view's action context, the .documents property is also available, and you know which documents are currently selected.

Charles Robinson said...

Why not put the parent UNID on the dialog form (you're copying txt_Key already) then add CreateNewOrderCopy directly to the OK action on the dialog form? That skips the interim step of writing to the notes.ini.

Charles Robinson said...

As a side note, I'm a little slow so it took me a few minutes to track down the relevant bit of code. If anyone else has a hard time tracking it down, Copy An Order is a hotspot on the Order form, Shopping Cart tab. The hotspot has two functions (below the Terminate event), GetSelectedDocument and CreateNewOrderCopy.

Mike Kretzler said...

If you use the actual Dialog box feature here (I haven't looked at the code), then you could put the selected document UNID into a field on the dialog. If that field were also available on the parent form, it would be updated automatically when you closed the dialog. That would skip writing and reading Notes.ini.

Chris Blatnick said...

Good comments, folks! As a side note, I used to be a little squeamish about writing to environment variables myself. Not really sure why...it just seemed to be something that was not quite right. However, I don't have that reservation anymore, as I've never once had a problem writing or reading from the notes.ini.

Keil Wilson said...

Nathan, I'm not following what you're suggesting there, but I was thinking of using your new ideas to re-do this example and (hopefully) make it stronger, faster, better.

Charles and Mike, from the OK action in the context of the dialog box form, I could not get a handle to the selected documents in the embedded view. The dialog box form just has no idea what's going on in the embedded view.

And thanks Charles for pointing out the location of the code. Trying to clearly describe this example was a lot harder than I though it would be.

Charles Robinson said...

Keil, I meant from the OK action in the embedded view on the dialog form. :-) When you put the txt_Key on the dialog form you can also put the UNID of the parent doc. Then in the OK action in the embedded view on the dialog you can get the dialog doc (uiws.CurrentDocument) and extract the parent UNID. Then use that to get the parent doc and do all the processing you do on the parent document directly from the dialog.

Darren said...

Great work Keil. In relation to this post and a comment here I've done some very preliminary proof-of-concept into searching from within a dialog box, displaying the results in the dialog box in an embedded folder, and now, using your method, passing the results back to the parent document.

This has probably been done before but if not a demo can be found here.

Feri said...

when I read about "embedded view", picklist on steroids was my first idea of implementation. thanks for sharing it, but I am little sceptical about it. does it follow these expectations?
- enter hotkey
- ability for multiselection
- show single category

Keil Wilson said...

Darren, very cool idea. I love how it asks you if you want to narrow the search if too many docs are returned. I can imagine how you did that, but I'd love to read/hear you explain it sometime. Thanks for sharing.

Keil Wilson said...

feri, because it's an embedded view, you can use all of the standard embedded view features (I just don't in this example). As for the Enter hotkey, that is something I overlooked. In the sample database, that does not work properly and probably should be handled. Also, if you wanted to use multiple selection, you'd have to return a string list of the selected document's UNIDs to the .ini file. Then you would have to process that list back out into individual UNID's later in the process.

Keil Wilson said...

As a followup, I spent a little time trying to figure out how to handle the Enter hotkey problem. Apparently, when the user presses the Enter key in the dialog box, that event is handled by the dialog box itself (which means that the embedded view context is unavailable). In the sample database, when you press Enter in the dialog box, it behaves like a Cancel (like you clicked on the "X" in the title bar). The only way I could trap the Enter hotkey was to add a hidden button to the form, make it the default (using the button properties), and give it a formula of the empty string (""). I guess you could write some code in here to response in any of a number of way, but what you can't do is determine which docs the user selected in the embedded view.

Darren said...

@Keil - Sorry it's taken a while to respond.

The code to check the results count is quite simple (the PropertyQuery field computes the FT query):

Set lcoResults = ldb.FTSearch(ldc.PropertyQuery(0), 0)

If lcoResults.Count > 100 Then
liResponse = Msgbox (Cstr(lcoResults.Count) + " properties were found. Do you wish to change your search criteria?", 4 + 32, "Results")
If liResponse = 6 Then 'yes, so exit
Exit Sub
Else
' continue
End If
Elseif lcoResults.Count = 0 Then
Msgbox "No results found. Please try again.", 0, "Results"
Exit Sub
End If

Slawek Rogulski said...
This comment has been removed by the author.
Slawek Rogulski said...

Keil, regarding the placement of the buttons. You can use a normal button placed on the form. There is no need to use view actions. This way you can have a consistent look.

Basically, place a hidden field on the form. Call it ButtonAction or something like that. Than the buttons will set that field with "OK" and "Cancel" or something similar for the OK and Cancel buttons respectively. After setting the field both buttons will then close the dialog. Now that the form is being closed the QueryClose event on the embedded view gets triggered. In this event you get a handle to the uidoc (dialog) by calling CurrentDocument from the workspace. And you check the contents of the hidden field. Then you act accordingly. If there was some validation that did not pass (done in script not in the field validation events) you simply set Continue to false on the QueryClose of the view.

I would actually do the copying of the order in the QueryClose event of the embedded view. After all you have a handle to the selected document and can easily get a handle to the main document so its all there. And there is no need for environment variables.

Keil Wilson said...

Slawek, you know, I couldn't consistently get the QueryClose of the embedded view to fire. It was strange. If I called ws.RefreshParentNote before closing dialog (to ensure those values were passed back to the button on the document from which I called the dialog, then the embedded view's QueryClose wouldn't execute. Admittedly, I didn't have a lot of time to play with this idea, as the end of the project was quickly approaching. Any further ideas are greatly appreciated. Thanks all for chiming in on the idea.

Michael said...

Thanks for the inspiration.

I had a project request to create documents in an embedded view, and the picklist just didn't cut it.

Your blog inspired me to use dialogs with embedded views instead.

The dialogs provided a much friendlier user interface with dynamic and static instructions.

I ended up using dialogs with embedded views launched from the action bar of the embedded view of the main form.

The records I am creating allow for multiple selections from each dialog (the dialogs are actually displayed multiple times and each allow for multiple selections)

I am amazed I got it all to work in the end.

What happens ...

In dialog 1 the user selects single or multiple documents each is an "origin point".

For each document selected in dialog 1 present dialog 2 and ask the user to select "destination point"

For each lane (origin - destination pair) selected above create a "lane" document in the original embedded view from which the action was launched.

It actually gets more complicated as i will need to present some additional dialogs to select other values ...

but thats another story

Thanks again for showing whats possible.

Ledlincoln said...

Slawek, that seems to be a good theory, but when the action is initiated from a button on the form, I don't get anything in db.UnprocessedDocuments. Apparently, that has to come from a view action.

vladimir kocjancic said...

Found an interesting problem if embedded view is located in another database.
In that case, actions should not assign additional NotesUIWorkspace objects as they will corrupt your local one (don't ask why, as I don't know). You need to code OK and Cancel buttons in @Formula language to avoid this issue.

Lenni Sauve said...

I must be misunderstanding some crucial bit of knowledge. I have a form with an embedded view, from which a user would select multiple documents, I can obtain the information I need from the documents, but I can't seem to find a way to bring that information back to the original form.

Here is my code. Can somebody please point me in the right direction?

(I have Keil Wilson's author information at the top of the code)

Sub Click(Source As Button)
Dim ws As New NotesUIWorkspace
Dim uidocParent As NotesUIDocument
Set uidocParent = s.CurrentDocument
Dim item As NotesItem
Dim messagelist As String
Dim count As Integer
Dim temp As String
count = 0
Dim session As New NotesSession
Dim dbCurrent As NotesDatabase
Dim ndcSelected As NotesDocumentCollection
Dim docSelected As NotesDocument
Set dbCurrent = session.CurrentDatabase

Set ndcSelected = dbCurrent.UnprocessedDocuments
Set docSelected = ndcSelected.GetFirstDocument

While Not ( docSelected Is Nothing )
Set item = docSelected.GetFirstItem( "dspCRNum" )
messagelist = messagelist & item.Text & Chr(10)
temp = "ARS_Selected" + Cstr(count)

Call session.SetEnvironmentVar(temp, docSelected.NoteID)
count = count + 1
Set docSelected = ndcSelected.GetNextDocument( docSelected )
Wend

Messagebox "These documents have been selected: " + messagelist

End Sub

Whenever I try to access uidocParent, I cannot grab any information from it or update anything to it.

Lenni Sauve said...

My co-worker found the answer, so everything is working well.