Wednesday, November 18, 2009

Sweet Drag and Drop Actions In the Lotus Notes Client

I've mentioned once or twice before that I really like using drag and drop. Although it is often a bit slower than other actions and you sometimes have to show the user how to do it, I find it to be a preferred method for executing certain tasks in an application. Lotus Notes itself offers many cool and useful drag and drop actions. I've been showing end users some of these for a long time, and always there are folks that had no clue drag and drop actions were even available. I wanted to share a few of them with you now.

Drag & Drop Links

You can drag and drop a document from any Notes view or a database icon into a rich-text field and Notes will create a document link or database link (as applicable). If the document is open, you can actually perform the drag and drop action from the tab. This is a much easier way to teach users how to send links to documents.

A couple of hints: You can often drag information from one part of Notes into another by hovering over the appropriate tab. For example, click and hold the tab of an open document, then drag it over to the tab of a document in edit mode. Hold it there for a second and the focus will change to the new document. You can then perform the drop action into a rich-text field and the link will be created. Also, an easy way to get the Notes:// address of an element is to perform that drag and drop into a regular text field. This will give you the Notes:// link along with the title of the item.

Drag & Drop Attachments

You can take attachments from a Notes document and drag them to the file system or your Windows desktop. For those of you on Notes 8, check out the very cool File Navigator side shelf component on OpenNTF. You can use this to drag and drop files directly from your Notes client!

Drag & Drop To/From Sametime

With Notes 8.5, you can drag a mail message to the Sametime Contacts list. When you drop it, a dialog will appear and present you with a list of all recipients of that e-mail message. You can choose to add them all or just select individuals to your contacts. This is a very speedy way to create groups within Sametime.

Going the reverse direction, you can drag a single contact or a group name from your Sametime Contacts list into the address fields of a new memo to instantly add those recipients to the mail message.

If you drag and drop a contact into a rich-text field, a mailto link to that individual will be created.

Drag & Drop Text

With Notes 8.5, users can now have a more rich experience as they drag & drop text. You can drag and drop text within a rich-text field (useful for rearranging some paragraphs, for example) or even between rich-text fields. I'll often use this when I am creating a new calendar entry and I want to pull some information out of an existing doc. Sure copy and paste is a bit faster, but drag and drop is so fun! ;-)

Drag & Drop Tabs

I often work with several documents and views open at one time and I generally like to have an orderly flow to how the tabs are arranged. If I open up these elements in an order other than the way I like them arranged, I just grab the tabs and reorder them via drag & drop. Perfect.

Drag & Drop Table Controls

There are some long awaited functions now available for manipulating tables via drag & drop. One of the best is the ability to resize columns without using the ruler. With Notes 8.5.1, you just grab the column border and drag & drop to resize.

You can also move whole rows or columns at one time via drag & drop. If you hover over the outside border of a row or column, you'll see the cursor change to an arrow pointing at a row or column respectively. Click, hold and drag and you'll see a shadow moving to represent that row/column. Drop it where you want it in the table and the other elements will be reordered accordingly (see animation below).

If you just want to move the content of a single cell, highlight it and then drag & drop it into the destination cell of choice. You can move multiple cells at once, as well. Play around with this a bit and you'll find some interesting functions.

While I'm on the subject of tables, while not strictly drag & drop specific, there are a couple of other very handy new tricks you might like. If you want to add a new row or column, you can now do this without resorting to the Table menu. To do this, hold the Shift key down and then hover over a row/column border. You'll see the cursor change to a thick horizontal or vertical line with a little blue diamond next to it. When you see this cursor, double click with your mouse and the new row/column will be inserted. Thanks to the developers...I really like that new edition!

So, those are some of my favorite drag & drop features in the Notes client. There are many more. How about you? Any particular drag & drop actions I missed that you really like? If so, please share in the comments.

Labels: , , ,

permalink | leave a comment

Friday, July 31, 2009

Add Document Ratings To Your Notes Client Designs

Way back when I was actively developing stuff on a daily basis, I wrote about a quickly cobbled together idea I had to include a document rating system in a Notes client application (might want to read that first). Document Rating is a familiar "Web 2.0" design pattern that can actually be quite useful. Document ratings allow users to democratically decide on good content and by using this technique the best stuff will bubble up to the top (at least theoretically). In the original post, I wanted to explore the concept of using editable view columns with icons in a little more detail while showing off some cool functionality in the client. With the frequent use of this design pattern in modern websites, I think it is safe to assume that it is a pattern that is around to stay. Thus, I wanted to make the solution a little more robust. As with most of the things I put here on Interface Matters, I've removed a lot of the extraneous code that you really should use in production so that you can focus on the core of the technique, but the sample database below should be enough to get you started.

Before we begin going through the technique, I suggest you read through the Rate Content design pattern page on UI That page should give you a little more detail on what I am shooting for. For our purposes, let's focus on the following mechanisms:

* Voting mechanism.
* Display the average rating an item has received.
* Display explanatory comments from users rating an item.

I've left off the three mechanisms below, but they seem like a perfect fit for dashboard-like functionality.

* Show the highest rated items.
* Favor quality items.
* Related items.

In the original post, I wasn't taking into account security on the document that is being rated. Thus, this technique really wouldn't work for most real world situations. To overcome this, I decided to use the concept of stub documents to capture each user's rating of a particular document. This offers several advantages. First, I can easily perform a lookup to see if the user has already rated a given document or not. Second, it allows me to extend the original functionality by allowing for additional information such as comments. Finally, this idea respects the security of the main document, as the average rating is calculated by an agent so that the user never has to have edit access to it. Thus, the flow of the process goes something like this:

1. User clicks a star to define their rating.

2. If desired, pop up a dialog box to capture their comments about why they rated the document as they did.

3. Write a stub document that captures a unique key value that corresponds to main document and user, their rating and comments.

4. Run an agent (that executes with proper authority) to update the main document with the new average rating. This agent will loop through all of its rating documents to determine the average.

At the risk of making this post too long, I am going to break this down step-by-step:

Step 1: Add columns to your view. I created a separate column for each star, since I want to know explicitly which one was clicked. Set the column to be editable and set it to "Display values as icons".

Step 2: The column value checks the current average rating of the document and based on the position of the column determines if it should show the filled or unfilled star.

@If(num_Rating >= 1; "star_red"; "star_open") and so on...

Step 3: Add the code to the InViewEdit event. I've included a bunch of comments to the code so you can follow what is happening. It's here that we first check to see if the user has rated the document previously and decide to allow them to edit that rating or not. I chose to implement this as a flag that you can enable or disable in the code itself for easy demonstration, but you could make this and some of the other options part of the db configuration. If the user hasn't voted yet, the InViewEdit code creates the stub doc, asks them for a comment (if appropriate) and kicks off the update agent.

(Please note I added some line breaks into the code samples. If you want to copy and paste, do so from the sample database)

Sub Inviewedit(Source As Notesuiview, Requesttype As Integer, Colprogname As Variant,
Columnvalue As Variant, Continue As Variant)

Dim workspace As New NotesUIWorkspace
Dim session As New NotesSession
Dim db As NotesDatabase
Dim doc As NotesDocument
Dim caret As String
Dim flag As Boolean

'Set AllowChanges to 0 to keep users from changing their rating. If set to 1, then can modify their initial value
Const AllowChanges = 1

'Get the CaretNoteID - exit if it does not point at a document
caret = Source.CaretNoteID
If caret = "0" Then Exit Sub

'Get the current database and document
Set db = Source.View.Parent
Set doc = db.GetDocumentByID(caret)

Dim ratingsView As NotesView
Dim ratingsDoc As NotesDocument
Dim DocUNID As String
Dim key As String

'We need to check to make sure the user hasn't already rated this document
Set ratingsView = db.GetView("viewRatingsByKey")
DocUNID = doc.UniversalID
key = DocUNID & "_" & session.CommonUserName
Set ratingsDoc = ratingsView.GetDocumentByKey(key)

If Not (ratingsDoc Is Nothing) Then
If AllowChanges = 1 Then
flag = workspace.DialogBox("dlgRatingsCommentDialog", True, True, True, True, False, False,
"Enter A Comment About Your Rating"
, ratingsDoc, True, True, False)
If flag = True Then
Call ratingsDoc.Save(True, False)
Call RunTheAgent(db, doc) 'Call the agent to update the main document with the new rating value
End If
Messagebox "'ve already rated this document"
End If
Exit Sub
End If

'If we got this far, the user hasn't voted yet, so we'll take their entry. Here we are creating the new stub document.
'This document uses a combination of the main document's UNID and the user name for the key value. This
'stub document could capture as much information as you need...just add the appropriate fields. If you don't
'ever plan to show the underlying content to the end users, you don't need an actual backend form.
Dim newRatingsDoc As NotesDocument
Dim item As NotesItem
Dim readersItem As NotesItem
Dim newValues( 1 To 2 ) As String
newValues( 1 ) = session.UserName
newValues( 2 ) = "[Admin]"

Set newRatingsDoc = db.CreateDocument
newRatingsDoc.Form = "frmRatingsDoc"
newRatingsDoc.txt_TargetUNID = DocUNID
newRatingsDoc.txt_RatingComment = ""
newRatingsDoc.txt_Key = DocUNID & "_" & session.CommonUserName
Set item = New NotesItem(newRatingsDoc, "nam_UserRated", session.UserName, NAMES)

'To keep ratings private, uncomment the line below. But if you do this, probably no use asking for comments.
'Set readersitem = New NotesItem(newRatingsDoc, "read_Users", newValues, READERS)

Select Case Colprogname(0)
Case "Star1"
newRatingsDoc.num_Rating = 1
Case "Star2"
newRatingsDoc.num_Rating = 2
Case "Star3"
newRatingsDoc.num_Rating = 3
Case "Star4"
newRatingsDoc.num_Rating = 4
Case "Star5"
newRatingsDoc.num_Rating = 5
End Select

'Modify the code for comments as necessary to suit your needs

'Now lets see if the user wants to add a comment
Call ratingsView.Refresh

'Option 1 - Use this if you want to pull up the comment dialog but don't require a comment.
'Call workspace.DialogBox("dlgRatingsCommentDialog", True, True, True, False, False, False,
"Enter A Comment About Your Rating", newRatingsDoc, True, True, False)

'Call newRatingsDoc.Save(True, False)

'Option 2 - Use this if you want to require comments before a rating can be saved.
flag = workspace.DialogBox("dlgRatingsCommentDialog", True, True, True, False, False, False,
"Enter A Comment About Your Rating"
, newRatingsDoc, True, True, False)
If flag = True Then
Call newRatingsDoc.Save(True, False)
End If

'Call the agent to update the main document with the new rating value
Call RunTheAgent(db, doc)

End Sub

Sub RunTheAgent(db As NotesDatabase, doc As NotesDocument)

'Call the agent to update the main document with the new rating value
Dim agent As NotesAgent

Set agent = db.GetAgent("agtCalculateRatings")
Call agent.RunOnServer(doc.NoteID)

End Sub

This LotusScript was converted to HTML using the ls2html routine,
provided by Julian Robichaux at

Step 4: Add the agent that updates the main document with the average rating. This agent is called by the InViewEdit code as described above. This agent will take the main document, find all of the stub rating documents based on the UNID of the main doc and then loop through the collection, simply adding the values of the ratings and then dividing by the number of ratings to get the average. This average value is then written back to the main document. Make sure that this agent is set to run with an id that has access to all of the main documents, not as the current user.

Sub Initialize

Dim session As New NotesSession
Dim db As NotesDatabase
Dim agent As NotesAgent
Dim ratingsView As NotesView
Dim mainDoc As NotesDocument
Dim ratingsDoc As NotesDocument
Dim ratingsCollection As NotesDocumentCollection

Set db = session.CurrentDatabase
Set agent = session.CurrentAgent
Set ratingsView = db.GetView("viewRatingsByUNID")
Set mainDoc = db.GetDocumentByID(agent.ParameterDocID)
Set ratingsCollection = ratingsView.GetAllDocumentsByKey(mainDoc.UniversalID)

If (ratingsCollection.Count = 0) Then
'Whoops...something went wrong and you'd do some good error trapping here
Exit Sub
End If

Dim ratingsCounter As Long
Dim numberOfRatings As Long
Dim averageRating As Long

ratingsCounter = 0
numberOfRatings = ratingsCollection.Count
Set ratingsDoc = ratingsCollection.GetFirstDocument

'Let's add up the total of all ratings first
Do While Not (ratingsDoc Is Nothing)
ratingsCounter = ratingsCounter + ratingsDoc.num_Rating(0)
Set ratingsDoc = ratingsCollection.GetNextDocument(ratingsDoc)

'Now, we average the ratings by taking the total and dividing by the number of votes
(assuming all have the same weight)

averageRating = ratingsCounter / numberOfRatings

'And now we can set this on the main document and be on our way
mainDoc.num_Rating = averageRating
Call mainDoc.Save(True, False)

End Sub

This LotusScript was converted to HTML using the ls2html routine,
provided by Julian Robichaux at

There are a lot of ways in which you could flush this technique out even more. For example, I added an embedded view to the main document form that is used to show the stub document ratings. This way, when you open a particular content document, you can see in detail who rated the content and why (via their comments). This is a nice way to provide users with real meaning behind the values. I'd be interested in hearing about other ways you might use this functionality as well.

The sample database is available below. You've likely seen this before, as it contains a variety of sample design patterns, some of which I haven't talked about yet. You can switch to the "User Ratings" view by using the selection box in the lower right corner of the main screen. Yes...this database is a bit ugly. I was playing with some different theme ideas and didn't want to make another boring looking Notes app. ;-)

Have fun, let me know what you think and please share with the others if you find a use for this in your company's applications.

Labels: , , , ,

permalink | leave a comment