When you're designing a Notes application, the way you want to ask for a value on a form is sometimes different from how you want to store it. Situations in which this might be the case include:
- A combobox list is used to select a value (1 through 10) that must be stored as a number.
- A telephone number or SSN is entered into three short "OS style" fields, but a field with all three parts concatenated with dashes is needed for views.
- A date is selected from a combobox but must be stored as a true date/time value.
The usual way to address a requirement like this is to create one or more editable fields for the user, and a hidden computed field where the "real" value is calculated based on the editable field(s). While this approach works, it has certain drawbacks. For one thing, it's a waste of space in your database. You end up with two fields that must be kept consistent, making it more complicated to write agents that manipulate your data, do integration with external databases, and so on. Every time you change one field, you must make sure to change the other(s). And, if the form design changes, you have multiple places that need to be updated. In addition, for best performance of your application, the fewer "summary" field values you have the better. Finally, certain values (dates in particular) are displayed differently by default depending on the user's workstation settings. It's better not to store a date value in text form in the editable field, but calculate the display using the current user's settings.
The question, then, is how to avoid storing the "editing" fields. There are a few ways I've seen this done, none of which is ideal:
- You can use an input translation formula in a field to change it to a different type from the defined type of the field (@IfError(@ToNumber(@ThisValue); "") ). This fails because the user interface will not translate a number back to text to (for instance) display it in a combobox field. Instead, you get a "normal" field when you open the document. Also, there may be difficulties with the full-text search, Lotus Enterprise Integration functions, and others, since the type of the data doesn't match the declared type of the field.
- You can use input translation formulas in the editable fields to change their values to @Unavailable as the document is being saved or mailed. When the document is opened, the field has no value, so its default value will be recalculated -- and you can base this formula on the computed field. However, if you save the document without closing it, the editing field is erased, so users can't see the value they just saved. The default formula is not recalculated even if you refresh the document; saving a second time would actually erase the value from the computed field as well.
- You can write Postsave code or a scheduled agent to remove the extra fields after the document is saved -- but saving the document twice each time it's edited is extra overhead that hurts the performance of your views. It's better to just prevent the field from being saved in the first place.
- The editable field is not on the form. Instead, you add a button that opens a dialog box. This lets the user edit a field. The hidden computed field is calculated by the dialog form based on their input, and the calculated field is returned to the document when they press OK. The editable field, which wasn't already in the document, isn't returned because the "no new fields" option was selected in the Dialogbox call. This is actually a good way to do this for some applications, but it's extra mouse clicks and an irritation for the user if there's no other reason for the editing to happen in a dialog. This would be appropriate, for instance, if you're prompting for information in response to the user pressing an action button; you had to use a dialog anyway.
Generally, I prefer to use the SaveToDisk property of NotesItem to tell the Notes client to not save the editable field. For this, you can write a little subroutine to disable saving of specified fields:
Sub SetNoSave(Source As Notesuidocument) Dim doc As notesdocument Set doc = Source.Document Dim item As NotesItem Set item = doc.GetFirstItem("DesiredDate_e") item.SaveToDisk = False ' repeat the above two lines for each field you don't want to save. End Sub
Call this function at any point before the document is saved. Then when it is saved, DesiredDate_e will not be written to disk, and it will not be erased from the screen, so the user can continue to edit the document.
For this to work correctly, you also need a Default formula in each of the "non-saved" editable fields. In this example, DesiredDate is a computed date/time field, and DesiredDate_e is an editable combobox field that lets the user select from only certain dates. So the Value formula for DesiredDate is @IfError(@ToTime(DesiredDate_e); "") and the Default formula for DesiredDate_e is @Text(DesiredDate).
It's best to call SetNoSave from a form event -- not from a "Save" action button, for instance, because the user can press Ctrl+S or Esc to save the document without using the action button. There's no one right event to call it from, but Queryopen is too early -- the "items" do not yet exist if it's a newly composed document -- and of course Postsave is too late. My preference is to call it from the Postopen routine, whether the document is in edit mode or not (because it may be put into edit mode later). That way, you only need to add code in one place, and you should be covered even if the application does a back-end save without putting the document into edit mode.
In a Web application, this technique will also work in a WebQuerySave agent. But it's easier to just delete the unwanted items using Call doc.RemoveItem("itemname").
There are a few caveats if you try this approach:
- If you just want to have one set of text values in a keyword list and another set of text values actually stored (for instance, the user sees "Yes" but you store "Y"), then using LotusScript is overkill. We suggest you read about keyword synonyms in Domino Designer help.
- If the field you want to not store is not editable and is not rich text, you also don't need this technique; just make the field "Computed for display."
- The NotesDocument.ComputeWithForm method will not execute your event code, so after using ComputeWithForm, you must explicitly delete the unwanted items from a document:
- This technique will work on rich text fields as well as "normal" fields. However, the rich text item doesn't exist yet in the Postopen event of a new document. To make this work:
- Add a default formula to the rich text field (even if the formula is just "").
- When the document is being composed, use NotesUIDocument.Refresh(True) to load the rich text field into the back end. Here's the code you would add to SetNoSave:
If Source.IsNewDoc Then Call Source.Refresh(True).
- Besides loading up the rich text item, the Refresh method will also execute all the input validation formulas. So you need to program them to not give errors when the document is just being refreshed:
@If(@IsDocBeingRecalculated; @Success; <the rest of your validation>).
- Adding this code will not delete the unwanted fields from existing documents until those documents are edited and saved. You may want to write an agent to "clean up" the fields:
REM "Run on all documents and...";
SELECT Form = "Whatever" & @IsAvailable(DesiredDate_e);
FIELD DesiredDate_e := @Unavailable
Before you run such an agent, think about whether or not your users will fuss about you messing up their electronic signatures and unread marks. Sometimes it's better to leave the old data alone if it's not harming anything.
- The developerWorks: Lotus article, "Working with groups in LotusScript, by the same author, describes how you can work with groups in LotusScript, using the built-in NotesAdministrationProcess class as well as two custom LotusScript classes.
- See the Lotus Domino Designer help for full descriptions of the formulas discussed in this article.
- Get involved in the developerWorks community by participating in developerWorks blogs.