ShowTable of Contents
Introduction
Beginning developers of Notes client applications, are sometimes perplexed by how to control the user's experience of editing documents when the Notes client has so many different options built into its user interface for navigating documents, editing and saving. They want to intercept user keystrokes or mouse clicks, to prevent the Notes client from interpreting them in the usual way. Often, a novice developer will add an "Edit" action button on a form, and will try to force users to click the Edit button as the only way to edit the document, or the Save button as the only way to save, because they want to make sure some code that is in the action, executes in those situations.
This is hopeless, because there are many ways for users to edit and save documents, and no way to directly intercept keystrokes and mouse clicks. The best way to control editing is to use the database ACL, Authors fields, controlled access sections, document locking, electronic signatures, and form event code, as appropriate. Which techniques you use depend on what the goal of your restriction is and the degree of security you require.
Many Ways to Edit a Document
If a user technically has access to edit a document, they can start to do so in several different ways:
- By clicking the form's Edit action button, if any.
- By double-clicking the document while it's open in read mode, including in the preview pane. This assumes the double-click to edit option is enabled in the user's preferences.
- By pressing Ctrl+E (or the equivalent on their OS) while the document is open in read mode.
- By clicking the "Edit Document" toolbar icon while the document is open in read mode.
- By pressing Ctrl+E while the document is highlighted in a view.
- By clicking the "Edit Document" toolbar icon while the document is highlighted in a view.
- By writing LotusScript, formula, or Java code that uses the Notes UI functions of these programming languages to open the document in edit mode.
There are probably a couple I've missed. The point of this list is to convince you that trying to prevent editing, or to control what happens when they start editing, by trying to control which of these methods you allow to work, is a waste of time. That's the wrong point at which to try to exercise control. Just accept that Ctrl-E, double-click, and your Edit action are all going to do the same thing, and put your intervention at the point where all these converge.
Database ACL: Your Primary Access Control Mechanism
If you need to let certain users edit documents, and prevent others (including preventing them only with certain documents or at certain times), your first, best choice is the database ACL (Access Control List), in combination with Authors fields. This is the
only way to absolutely prevent users from editing documents when you don't want them to.
Users with Editor access or higher can edit any document. Users with Author access can only edit documents that list them in an Authors field before they attempt to edit the document. Users with less than Author access cannot edit any document. If a user doesn't have access to edit a document, they can't change it in any way (except to drag it to a folder, if you consider that a change).
It's a little tricky to get Authors fields to work properly. Some key points:
- You can use group names, role names, or usernames, or a combination. Roles are preferred, if the requirements of the application make them practical.
- If using usernames, use the canonical form ("CN=Frieda Vallis/OU=Petaluma/O=Yoyodyne") rather than the abbreviated or common name format.
- If there are multiple names in the field, make sure the field value is truly multivalued, and not a single string containing commas or other delimiter characters.
- If you're also using Readers fields, be aware that being listed in an Authors field also allows users to view a document.
- The value of the Authors field has to be stored in the document before the user tries to edit the document. A Computed for Display Authors field will never work, and a Computed Authors field will not let a user edit the document based on the value it would have if computed now, but only based on the value that was stored in the document when it was last edited. The value you compute for the field, controls who will be able to edit the document after you save it.
The References below include an article with much more detailed information for troubleshooting and correcting problems with Reader and Author fields.
When referring to a role in the database ACL, include the [square brackets]. The reason we often prefer to use roles rather than group names, is that it's a bad idea to include specific user or group names in the design of your application. For instance, suppose you have a computed Authors field such as the following:
REM {Don't do this - uses hardcoded group name};
@If(Status = "Draft" : "Rejected"; OriginalAuthor; "BTT Auditors")
This formula assumes that there's a Computed when Composed field called OriginalAuthor, containing the name of the document author. Depending on the value of the Status field, we assign editing access to the BTT Auditors
group. But since you've hardcoded the group name into your application, your design isn't reusable. You can't use this same application design for another department because they would want to have their own group name in there. And what happens if the group gets renamed? All those old documents must be modified, and how are the administrators (or the adminp process) supposed to know that your design has to be updated also?
That's why it's better to create an ACL role and make BTT Auditors a member of that role. If you use the role name in your formula, the application can be reused by another team, or the group can be renamed, and all that has to be updated is the database ACL. The documents that list the role name in an Authors field don't have to change; the same role now just refers to a different group. Your Domino administrator will like you better if you do it this way.
REM {Improved formula has no hardcoded user or group names; use the database ACL to control who [Auditors] refers to.};
@If(Status = "Draft" : "Rejected"; OriginalAuthor; "[Auditor]")
Controlled Access Sections
If you want to limit access to only certain parts of a document, allowing users to edit other fields, a controlled access section is the way to go. The References section contains a document explaining how to use them. You can put a Controlled Access Section around the whole document if you want to make no fields editable in certain circumstances.
Controlled access sections are not an absolute access control mechanism the way that database ACL and Author fields are. Users who know how to write some code can assign these fields through back-end methods, without needing to put the document into edit mode or open on screen at all. However, they are reasonably tight. If the security requirements call for being able to detect unauthorized modifications to controlled access sections, you might use signed sections. In most applications, the security requirements are not that stringent.
Document Locking
Use Document Locking when the purpose of your code is to prevent multiple users editing the same document simultaneously. Users with local replicas will be able to edit documents offline without checking out the document from the server first, but they will get a warning. So you may still have replication conflicts; they just would be a lot less likely. The nice thing about the document locking feature is that you just have to turn it on in the application properties panel-- you don't need to write any code (though you might want to add view column icons to show whether a document is locked).
Form Events
It is not appropriate to use LotusScript form events to control access to edit a document. Users who have copies of Domino Designer (which is free) can debug your LotusScript code and abort whatever test you're about to use to decide whether they are authorized. You can prevent this by hiding design of your application or putting the form event code in a %include file, but if someone's savvy enough to use the debugger on your code, they're probably also smart enough to just write their own code to manipulate your documents with back-end methods. Again, ACL and Authors fields are the only certain way to prevent unauthorized editing.
However, it is appropriate to use form events to do something helpful. For instance, if there are form fields which are populated from an outside source, then when the user starts editing you might want to plug in the latest values. Or you might just want to refresh the fields on the form. Or whatever. The correct place to put such code is not in an action button, because there are many ways to start editing a document that bypass your action button. The right place is in the form events.
If the code you want to run would prevent editing, or would need to run before the form actually opens or enters edit mode, it needs to be in the Queryopen and Querymodechange events. It needs to be in both places because there are two basic ways to get into edit mode: by opening the document in read mode first, then switching to edit mode. This would trigger the Querymodechange event. However, if the user opens the document via Ctrl+E from a view (for instance) then it opens already in edit mode, so that there is no mode change event. In that case you would have to catch it in the Queryopen.
If the code needs to run immediately after the document is placed into edit mode, you would put it in the Postopen and Postmodechange events. The reason for putting it in both places is the same as for the Query logic.
Because it's a bad idea to duplicate code, you would probably put the code in a subroutine, which you would call from the two places. For instance, the following LotusScript code puts up a warning if the user starts editing a document whose Status field has the value "Completed".
' Globals:
Option Public
Option Declare ' Always use Option Declare!
%INCLUDE "lsconst"
Function PreEdit(Source As Notesuidocument) As Boolean
' return False if editing should not proceed.
Dim doc As NotesDocument
Dim result%
Set doc = Source.Document
If doc.GetItemValue("Status")(0) = "Completed" Then
result = Msgbox("Document is in COMPLETED status. Are you sure you want to edit?", _
MB_YESNO + MB_ICONEXCLAMATION + MB_DEFBUTTON2, "Edit Confirmation")
End If
PreEdit = (result <> IDNO)
End Function
Sub Queryopen(Source As Notesuidocument, Mode As Integer, Isnewdoc As Variant, Continue As Variant)
If Mode And Not Isnewdoc Then
' user opening existing document in edit mode -- make sure that's OK.
Continue = PreEdit(Source)
End If
End Sub
Sub Querymodechange(Source As Notesuidocument, Continue As Variant)
If Not Source.EditMode Then
' if changing FROM read mode, perform pre-editing checks.
Continue = PreEdit(Source)
End If
End Sub
Now
please note this is
not the way to do things if you want to absolutely prohibit editing of documents at Completed status. In that case, use Authors fields, and maybe as backup against editing by the database manager, a read-only form that you change the Form field to when you change the status to Completed, and electronic signatures to detect tampering. But where you want to do something helpful for the user, like confirm that they really want to do this unusual thing, or update fields that are sourced from elsewhere, form events are a good way to go.
Form events can also be written in formula language or JavaScript, but for the 'Query' events, LotusScript is the only language that gives you a way to stop the user entering edit mode.
Trapping the Save Event
Similarly to the Edit function, developers will sometimes add code to a Save form action, intending that that code will execute every time the user saves a document. Generally, it's better not to put this code in the action button.
It completely makes sense to have one or more action buttons that perform some special function in addition to saving the document, but generally it's better to also let people press Ctrl+S or Esc as a "normal" or default way to save also. For instance, you might have an "Approve and Exit" control that places the document into edit mode, takes some workflow action like setting the Status field, saves changes and closes the document. But if they just press Esc, they can save the document without affecting the status.
NOTE: Never just "save and exit" in an action button; check whether the save worked before closing the window. If save fails and you try to exit anyway, Notes prompts the user whether to save -- when they just said they wanted to by clicking your button and after they've just been told that they can't save. Instead, "attempt to save and exit if that succeeds." Every function that tries to save the document, returns a status value to show whether it succeeded. Check that status value and only try to exit if the save succeeded. And if it failed, you may want to undo whatever change your action button made before trying to save.
If you don't have a special action like the one described above, but always want to execute some code just before or after a document is saved, use the Querysave or Postsave event of the form. These events always execute when a document is saved from the Notes client UI. So for instance, if every time a document is saved, you want to send an email notice to someone, the place to do it is in the Postsave event,
not the Save action button.
Another option for post-save actions is to write a server agent that runs on recently modified documents. This has a couple of advantages: if it sends email notifications, the email comes from a single address, simplifying rules-based handling in user mail files. The end user who is saving the document, doesn't have to wait for whatever is happening, resulting in better performance. And the end user can't mess up your code by debugging it with the LotusScript debugger.
NOTE: Users don't always close documents when they save. They may make further changes and save again. So it's generally not a good idea to have a server agent run immediately and make further changes to the document. That could result in save conflicts.
Advanced Topics
InViewEdit and Agent Changes
If you allow editing in-view using the InViewEdit function, bear in mind that the form fields will not be calculated automatically, and your form event code will not execute. Use ComputeWithForm method if you need to recalculate computed fields (or duplicate the function of the field formulas in your code). If you need to execute the same code that you've put in some form events, you can avoid duplication by putting the code into a LotusScript library so that you can call it both from the form, and from views.
If changes are made by a back-end agent, the same principles apply.
Restricted Exceptions to Access Control
In some cases of high-security applications, you might want to allow certain users to make specific types of changes to documents, without being able to make changes to all fields of the document by writing code. There are a few approaches for doing this:
- Create a server agent that runs at intervals to make changes for all documents in a given folder, or for documents pointed to by other documents to which the user does have access. Note that you can use access lists to control who can add documents to a folder.
- Create a server agent that runs on schedule "never," and set it to run with the agent signer's privileges. Give users the an action to invoke the agent with "RunOnServer" method, passing it the noteID of a document that tells it what they want it to do (it might be the document they want to act on, or a separate "parameter document" with field values containing other document IDs or whatever).
- Give the users access to edit the documents, but use electronic signatures to detect tampering.
Insist They Use the Action Button -- IF YOU MUST!
Please don't skip ahead to this section. Read the above. Think about your end users; think about how annoyed you would be if you double-clicked a document and nothing happened, or if the application told you you must use the Edit button. Wouldn't you feel that you were just being made to jump through hoops? Isn't it better to let the user do things in the way that they are most comfortable with, and have the application accommodate different ways?
If you still feel that you really must have an action button which is the only way to start to edit the document, or the only way to save the document, here's how to do it.
Define a variable in the (Globals - Declarations) section of your form design. It is available to all LotusScript code on the form. So for instance, if you want an Edit action button to be the only way to begin editing the document, you can do something like this:
' (Globals) code
Option Declare ' as always
Dim gbEditOK As Boolean ' FALSE by default
' (Form) code
Option Declare ' as always
Sub Queryopen(Source As Notesuidocument, Mode As Integer, Isnewdoc As Variant, Continue As Variant)
If Mode And Not Isnewdoc Then
' Never allow open directly into edit mode unless composing, because that doesn't use our button
Continue = False
End If
End Sub
Sub Querymodechange(Source As Notesuidocument, Continue As Variant)
If Not Source.EditMode Then
Continue = gbEditOK
gbEditOK = False ' reset trigger after each go.
' You might want to put up a messagebox if you're refusing to allow editing.
End If
End Sub
' "Edit" Action
Sub Click(Source As Button)
Dim wksp As New NotesUIWorkspace
Dim uidoc As NotesUIDocument
Set uidoc = wksp.EditDocument
If uidoc.EditMode Then Exit Sub ' already in edit mode, nothing to do.
' Here, do whatever you have to do before you begin edit, that for some unimaginable
' reason you can't do in the Querymodechange event.
gbEditOK = True
On Error Goto editFail
uidoc.EditMode = True
On Error Goto 0 ' or whatever other error handler you might have.
If Not uidoc.EditMode Then
editFail:
' attempt to enter edit mode failed. Undo whatever you did above that
' needs to be undone if they can't edit.
Exit Sub
End If
' Here, do whatever you have to do after beginning edit, that for some unimaginable
' reason you can't go in the Postmodechange event.
End Sub
SaveOptions Field
The SaveOptions field can be used to prevent people saving a document. If SaveOptions = "0", Ctrl+S doesn't work and the user isn't prompted to save when they try to close the document with unsaved changes. Read more about this in the product help.
References