This information was missing from the help, and since it took me a while to find I decided to post it here.
The question is, when specifying the "filters" for a file-browsing dialog, how do you group multiple file types together under one heading? E.g. under the description "JPEG files" you want to include both "*.jpg" and "*.jpeg". The answer is, ";" (semicolon) can be used as a delimiter.
Also, don't forget the wildcard "?" which matches any single character, and use "*" as the matching pattern for "All files" to match files that don't have a "." in their names.
So for instance:
I've sent in a request to have this added to the product help. (BTW: anyone can submit help feedback by clicking the link at the bottom of each help page. This is what I do. It works.)
Andre Guirard | 9 March 2010 05:54:29 PM ET | Home, Plymouth, MN, USA | Comments (2) | Permanent Link
I while back I wrote about Multiple values in multiple rows in multiple columns about how to make the column values match up when you have multiple lists of values that you want to display on separate rows. These might either be from having multiple multi-valued fields, or one multivalued field with another list that you derive from it by substitution.
For instance, in the view above, the icon column converts the list of Attributes to a list of icons, as follows:
_from := @Left(_chart; " ");
_to := @Right(_chart; " ");
@ToNumber(@Replace(Attributes; _from; _to))
(NOTE: An icon column can display multiple icons if its formula returns a list, but the list must contain only numbers, or only strings (referencing image resources). Macro language doesn't let you mix different datatypes in the same list)
So the values in the Attributes column, and the values in the icon column, match up by position. Now suppose you wanted a view sorted by Attributes, where if multiple attributes are selected, the goggie is listed multiple times, sorted by the attribute values. So we set the column of Attributes to "Show multiple values as separate entries" (VASE for short).
It's not hard to spot the problem; in the first row, the attribute "Bwave" should be represented by the star symbol in the icon column, but instead all of the icons for that goggie are displayed. It might be that's what you wanted; but let's suppose you would prefer to see only the icon for the attribute represented on that row.
The obvious solution is to set the icon column, also, to display multiple values on multiple rows. Here's the result:
It didn't work the way you might have hoped. Though column 2 is set to sort, it's only sorted by the groups established by column 1. But column 1 doesn't display all the different icon values; it only repeats the first one. This configuration, where there's an unsorted VASE column followed by a sorted VASE column, doesn't do anything useful. As the linked blog entry above shows, it's not a good idea to sort both VASE columns, and sorting only the first one results in a sort by icon number rather than attribute name.
Assuming you require the icon column to come first, the solution is to use a hidden VASE sorting column that duplicates the Attributes column, then do not sort the two other VASE columns:
Now the rows are sorted by Attribute, but the icon displayed near the attribute is the right one for that attribute.
Andre Guirard | 17 February 2010 10:00:00 AM ET | Home, Plymouth, MN, USA | Comments (0) | Permanent Link
I noticed someone in the forums complaining that the Notes client didn't have any option to reverse the case of text, and it occurred to me that it shouldn't be too difficult to write an agent or action button to do this. I've done it two ways, one to change all the text in the Body field, one to do selected text only.
Here's the one to change the whole Body field. Note this assumes nobody is writing in the Kaithi alphabet, which is used temporarily to represent lowercase letters while we find and replace the uppercase letters to lowercase.
aCode = Uni("a")
Dim wksp As New NotesUIWorkspace
Dim session As New NotesSession
Dim uidoc As NotesUIDocument, uidocNew As NotesUIDocument
Dim doc As NotesDocument
Dim rti As NotesRichTextItem
Dim strFieldname As String
Set uidoc = wksp.CurrentDocument
uidoc.Refresh True ' do this if rich text field is editable, to get current contents in case user has modified them.
Set doc = uidoc.Document ' get the back-end document for the document open on screen.
Set rti = doc.GetFirstItem("Body") ' insert your fieldname here, generally "Body"
Dim range As NotesRichTextRange
Set range = rti.CreateRange
Dim count&, ch$
For i = 0 To 25
ch = UChr(acode+i)
count = range.FindandReplace(ch, UChr(11080+i), RT_REPL_ALL)
Call range.FindandReplace(UCase(ch), UChr(acode+i), RT_REPL_ALL)
If count Then
Call range.FindandReplace(UChr(11080+i), UCase(ch), RT_REPL_ALL)
End If
Next
rti.Update
doc.SaveOptions = "0" ' make it possible to close document without save prompt.
If doc.HasItem("MailOptions") Then
savedMailOptions = doc.GetItemValue("MailOptions")
End If
doc.ReplaceItemValue "MailOptions", "0"
Call uidoc.Close(True)
Set uidocNew = wksp.EditDocument(True, doc, , , , True)
Delete uidoc
Set doc = uidocNew.Document
doc.RemoveItem("SaveOptions")
If Not IsEmpty(savedMailOptions) Then
doc.ReplaceItemValue "MailOptions", savedMailOptions
Else
doc.RemoveItem "MailOptions"
End If
If one did want to operate on selected text only, that's simpler to do because there are UI methods for it, but unfortunately, you lose any formatting changes within the selected text, and any non-text objects, such as embedded images or tables. You also lose the distinction between line breaks and paragraph breaks, which normally doesn't matter. But for what it's worth, here it is:
Dim uidoc As NotesUIDocument
Dim toFix$, i%, ch$, bChanged As Boolean
Set uidoc = wksp.Currentdocument
toFix = uidoc.getselectedtext
For i = 1 To Len(toFix)
ch = Mid$(toFix, i, 1)
If UCase(ch) <> LCase(ch) Then
If ch = UCase(ch) Then
Mid$(toFix, i, 1) = LCase(ch)
Else
Mid$(toFix, i, 1) = UCase(ch)
End If
bChanged = true
End If
Next
If bChanged Then
uidoc.InsertText toFix
End If
Andre Guirard | 10 February 2010 02:08:12 PM ET | Home, Plymouth, MN, USA | Comments (1) | Permanent Link
I'm trying to find an agent to help me sell my second novel (meanwhile I'm doing a little refitting on the first). In case anyone's interested to read some of it online, the first few chapters of The Goodnight Agency are available here (I use a pseudonym to keep my potential fiction audience distinct from my technical publications).
The site it's on, "authonomy.com," is kind of interesting. A lot of publishers have done away with their "slushpiles" and are only taking agented submissions. Harper Collins is doing something a little different. Apparently they too let all their slush readers go, but instead of relying totally on agents, they have the people who submit unsolicited manuscripts to them, do their slush reading.
If you go to this website, you can read however much of their manuscripts people choose to upload, and if you register, you can also "back" or comment on books you like, establishing a ranking among the uploaded works. Periodically, the editors will review whichever entries are at the top of the heap at that time, and make offers for any they like. It's an interesting system, though the rankings seem to be influenced more than a little by how willing people are to do horse-trading for points.
You don't actually have to upload a manuscript yourself, to sign up and vote for others' work, so if a writer has an existing audience, say from their blog or something, those folks could potentially register on the site and give the writer's work a boost. Only if they actually liked it, of course.
Andre Guirard | 3 February 2010 01:48:02 PM ET | Home, Plymouth, MN, USA | Comments (1) | Permanent Link
I'd like to add a Designer wiki entry with a collection of links to the best "Best Practices" documents in different areas. I'm looking for recommendations from you! Please tell me about resources in the following areas (ones I know about are already listed):
- Domino Applications integrating with external relational databases(oracle, db2, etc).
- Redbook: Implementing IBM Lotus Enterprise Integrator 6 is probably a little out of date, but describes both how to set up LEI, and also how to program custom integration using the LC LSX. Note that some LEI features are also included with the Domino server license (this subset of functions is called DECS).
Your additions...?
Andre Guirard | 25 January 2010 05:03:00 PM ET | Caribou Coffee, Plymouth, MN, USA | Comments (1) | Permanent Link
Using the Eclipse-based Domino Designer, because the Notes application corresponds to an Eclipse project, various functions are available that apply to Eclipse projects generally.
I had a request recently to export all the code in a project to disk so that an automated code-checker tool could be run against it. My initial thought was to use DXL, but the DXL representation of some design elements doesn't make it easy to extract the code. JavaScript libraries, in particular, are hard to decode.* So I looked for an alternative. Here's what I found.
- First make sure the database is in your Designer navigator (if it's never been opened in Designer from that workstation, open it).
- To view the Notes applications as Eclipse projects, you have to open the Project Explorer view of Designer. Menu Windows / Show Eclipse Views / Other , then select General / Project Explorer.
- Note: if you have as many databases in your outline as I have, it may take some time to populate this window. Be patient.
- Find your Notes database there.
- If it is not open (not expandable), right-click it and select Open Project.
- Right-click and select Export.
- Select File System as the export destination (it's under General) and click Next.
- In the next screen, you might choose to expand the project and deselect some things that you don't care about, for instance as highlighted here in yellow.
- Enter the directory you'd like to export to, and click Finish.
* We're working on it.
Andre Guirard | 7 January 2010 10:32:12 AM ET | Home, Plymouth, MN, USA | Comments (0) | Permanent Link
Notes/Domino developers sometimes need a keyword list that exceeds the Notes formula limitation of 64KB. In a Notes client application, without using picklist, how do you do this?
In the particular case, I had a view containing many names, and I wanted a dialog list field that would let me select from among them. Here's how the field is defined, then:
| keyword formula: @If(!@IsDocBeingEdited; @Return(@Unavailable); ""); _shorts := @DbColumn(""; ""; "shortLookup"; 1); @If(@Length(@ThisValue) >= 2; @DbLookup(""; ""; "Pirates"; @Left(@ThisValue; 2); 1; [PartialMatch]:[FailSilent]) : _shorts; _shorts ) Since the field is set to refresh fields on keyword change and refresh choices on document refresh, it refreshes its own choices when its own value changes. The view "shortLookup" is categorized with the formula @Left(Name; 2), so reading its first column returns a list of all the valid first-two-letters of valid selections. The view "Pirates" is the one containing all the valid choices, which we would just use @DbColumn on if that didn't return too much data. Once we have two letters to work with, the formula does a partial-key lookup to retrieve all the values that begin with those letters (but we always offer the full list of first-two-letters also, in case the user changes their mind). |
So from the user's perspective, if they open the helper dialog, they don't initially see a list of all the choices; they see all the first two letters. If they select from this list or type two letters, they'll then be able to see all the choices beginning with those two letters. But the list of all the first two letters are also still available in case they made a mistake.
Depending on your data you might choose more or less than two letters as the trigger for your initial lookup, or you might even do it in phases; two letters to get a list of all the first-six-letters beginning with those two letters, then once they get to six letters, all the choices matching those six. That's a little more complicated but the principle is basically the same.
Some points:
- To use this technique you must have a form designed to not mind refreshes -- for instance, validation formulas need to test @IsDocBeingRecalculated and avoid failing validation in that case.
- Because of frequent refreshes, this and your other lookup-based formulas also need to be efficient, testing the edit mode as we do here, and making sensible use of caching. I think Domino developers generally should actually pry out the NoCache key on their keyboards to avoid using it thoughtlessly.
- From a UI perspective, it might be nice to append an ellipsis to the _shorts value ( _shorts := @DbColumn(""; ""; "shortLookup"; 1) + "..."; ) to clue users in that the value is incomplete -- if you can do this without risking exceeding 64K.
- The input validation formula for this field will need to know to reject the two-character values.
Andre Guirard | 16 December 2009 11:30:13 AM ET | Home, Plymouth, MN, USA | Comments (3) | Permanent Link
I got a question in email recently that I thought should've been addressed in my performance whitepaper -- but it wasn't, so I figured it would be worth mentioning here.
The question: why does NotesView.FTSearch take so much longer than NotesDatabase.FTSearch to find the same set of documents?
What's going on: This excellent article describes the performance characteristics of different search techniques based on empirical testing.
The full-text index is based on documents and their contents only; it contains no information about views. Therefore, to do a full-text search within a view is a two-step process. The NotesView.FTSearch method must first do the search, which returns all the matching documents within the database. Then, it has to check each matching document to see whether the document occurs within the view. The benchmark article shows that the amount of time this takes, depends on how far down in the view the document appears, so it's apparently just iterating through the view entries looking for one that matches the noteID of each document in the result set.
Obviously, NotesDatabase.FTSearch doesn't do the second step of testing whether the documents appear in a particular view. So, you get your results faster, even if they might contain some documents you don't want.
If you decide to search the database instead of the view, you might need a more complicated FTSearch expression to limit the results to those you would have gotten from searching the view, and this might take longer (or might not even be possible -- the search expression can't do everything you can do with a formula). So performance-wise, you might still be better off searching the view, but it's worth considering.
If the view you would've liked to search contains nearly all the documents in the database, and if you were going to iterate through the results anyway, you might search the database with your original search expression, and just add a test in your code to skip over any documents that you would've wanted excluded. ( If doc.Form(0) = "FormIWanted" Then (process the document) End If )
A fourth alternative may be useful in some situations: because we have set operations on document collections, if the view you wanted to search contains all the documents except those in some other view (vwKeywords, say), you could first search the database, then get the collection of documents from vwKeywords, and use the Subtract method to remove them from the search results. The nice thing about it is that the iteration takes place in the fast C code, and you don't have to "crack open" a note to find out that you didn't want it. It seems to me this might be faster than a view.FTSearch in cases where the result set and the set of documents to be excluded are relatively much smaller than the documents in the view you wanted to search. Let's do the math for a sample case to see how this might work:
- The view you wanted to search contains a million documents.
- On average, you think 300 documents in the database might match your search.
- 295 of those are likely to actually be in the view.
- Your "inverse" view, containing all the documents that are not in the view you wanted to search, contains 4000 documents.
- NotesView.FTSearch must compare 295 results against an average of 500,000 view entries each to determine that they are in the view, and 5 results against 1,000,000 documents each to determine that they are not in the view, a total of (295*500000)+(5*1000000) = 152,500,000 comparisons.
- If you use NotesView.AllEntries to get the collection of 5000 entries in the inverse view and Subtract those from your NotesDatabase.FTSearch results, Subtract must compare 295 results against 4000 view entries each to determine they don't need to be removed, and 5 results against an average of 2000 entries to determine that they do need to be removed (this is assuming a very simple-minded algorithm, which is usually the safest assumption :-) ). That's 295*4000 + 5*2000 = 1,190,000 comparisons, a much more civilized number.
- Use NotesView.FTSearch("your search expression")
- Use NotesDatabase.FTSearch("(your search expression) and (additional expression that duplicates view selection formula)")
- Use NotesDatabase.FTSearch("your search expression") and then use a test in your code to skip over unwanted results.
- Use NotesDatabase.FTSearch("your search expression") and then if there's a view that contains all the documents you don't want in your results, get all the entries in that view and subtract them from your collection.
And of course, don't forget to consider alternatives to FTSearch. If a view search (NotesView.GetAllDocumentsByKey) can get you the documents you want, that's generally the fastest way.
Andre Guirard | 8 December 2009 10:45:14 AM ET | Caribou Coffee, Minnetonka, MN, US | Comments (1) | Permanent Link
With version 8.0, a database option ("Disable automatic updating of views") was added to let you tell the server's view-indexing process to skip that database. This is handy, but it would also be nice to have such an option on the view level.
What the database-level option does, is fail to update the indexes of any views until someone actually uses the view. This "lazy" updating means that when the user opens a view, if it hasn't been used in a while, there may be a lengthy delay while documents created or modified since the last use are filed into the view's index. This is a tradeoff -- for improved server performance generally, some lesser-used applications may be set to index only on demand.
The database level option doesn't totally override the view's indexing options. The "Manual" option is lazier still; the index never updates unless specifically requested, so it's not necessarily up to date when the user opens it. "Auto, at most every" works the same as Manual, except it will only let the view get a certain amount out of date before it forces a refresh -- the difference is that when the "Disable automatic updating" option is set, this check only happens when the view is in use.
So there's no option which will do for a single view, what the "Disable automatic updating" option does for a whole database. If such an option existed, it might be called "When used". But it doesn't exist -- in the UI.
But wait! If you use the back-end methods to modify the design note, and assign the item $Index = "/L", the view will never be auto-indexed, but will be updated when someone uses it. This option is auto-assigned to "server private" copies of "private on first use" views, to limit the burden to the server of maintaining these views. So if you want to, you can write code to adjust this property of views. With the Eclipse-based Designer, it could be a plugin that gives you a UI for the option (though not in the view infobox -- you might instead present it as a dialog listing all the views and letting the user change the indexing attributes with a checkbox).
We're considering adding this view option to the UI in a future release (as always, no guarantees).
Andre Guirard | 25 November 2009 10:55:09 AM ET | Home, Plymouth, MN, USA | Comments (1) | Permanent Link
I've created a LotusScript agent in a new database. I'd like to hide the code so that the customer cannot see it.
I found this help document that talks about hiding the design of a database, which also hides the LotusScript.
Is this the way to do it? Or is there a better way? Someone said I might have to create a library, but wasn't sure.
The short answer is, design hiding would work for what you're talking about, but it's a little complicated, so you might want to use a simpler alternative.
The simple alternative is to put your code in a .lss file on your hard disk, and use a %include statement in your agent. The design of the agent isn't hidden, but the source is not visible.
The down side to this is that the code isn't stored in an nsf anywhere, so you're basing your design on something stored on a local file system. This makes it possible that the source will be lost, or that you will have multiple developers working on the same agent with different versions of the code, and which one is used depends on who saved the agent last, and you can't tell what version is being run from looking at the agent.
However you manage to hide the source code, another thing you'll have to consider is that customers will be unable to edit the agent. If it's a scheduled agent this is inconvenient, since it means they can't change the schedule. So, rather than hide the source of the agent, you could hide the source of a script library containing the guts of the agent code, and have a non-hidden agent that "uses" the script library. The source code of the agent would be visible, but it would just contain a call to the processing function in the library, so you aren't giving anything away.
If you feel it's worth the extra work to use the design hiding feature, here's what you need to do.
To begin with, where the help says: 'Check both "Inherit future design changes" and "Hide formulas and LotusScript."' that is completely wrong. If you hide design you do not want to continue to inherit from the template.
Let's first consider the case where you want to inherit an entire design, instead of just one script library. It doesn't usually make sense to have one template with its design exposed, and a nsf file with its design hidden. You need a pair -- the nsf file and the template -- with both their designs hidden. This lets you manage the design of multiple applications from the template. Then, you also need a second template where the design is not hidden, so that you can still edit it. When you have a new version of the design in the non-hidden template, you use Design Replace to update the design of the hidden template but DO NOT continue to inherit.
Why not inherit changes? Because the hidden elements are different from the original elements -- in that they're missing the source code. The DESIGN task on the server notices that the design elements are not the same, so it updates all the design elements in the hidden template (or database). Then, because the design elements in the hidden template are supposed to be hidden, it strips the source code from them. But this means they are still not identical to the non-hidden template, so the next night all that work is repeated.
When you have a hidden template and a hidden NSF, this conflict does not occur. Because the design elements in the template don't contain source code, the source doesn't need to be stripped from them after they're copied to the NSF. So the design refresh process works just fine between these databases. This lets you maintain multiple copies of the app with a single template, which is good.
Now, let's suppose you want to hide just one design element. You still need a template with the entire design non-hidden. That's where you do your development work -- let's say its template name is One. You should also have another template with a complete copy of the design which is hidden -- its template name is OneHidden, and you update it via design replace, not checking the "continue to inherit" box for reasons described above.
Next, you need a template containing a mix of hidden and non-hidden elements -- its template name will be OneCust. You produce this template by initially creating the database from template One choosing not to hide design. For this one, you can continue to inherit from the template. Then you open the new template in Designer, find the design elements you want hidden, and change them to inherit directly from OneHidden. Now, when you refresh the design of the new template, the unhidden elements that you have flagged to inherit from OneHidden will be replaced by the hidden versions of those design elements. They can no longer be edited and the source code isn't in them.
Of course, can also make an NSF directly as described above for OneCust template, but it's nice to be able to pop off multiple copies of the application without having to do that work over, so it's useful to have a template.
An extra step for additional credit: One problem with delivering the template OneCust to customers, is that it contains references to the templates One and OneHidden, which the customer doesn't have copies of. This results in annoying server log messages from the DESIGN task complaining that these templates were not found. Therefore, you might want to have a fourth template containing the published design, which you would call OnePub. You create this from OneCust and choose to not hide design and not inherit changes -- and you put a release number on it.
So, to edit the design and make a new release, the procedure is that you create a test database from the One template with nothing hidden. You make your edits in the One template and refresh the design of your test database. Once it's right, you replace design of OneHidden -- yes to hide, no to inherit. And you refresh design of OneCust. And then you use OneCust to create a new OnePub (v1.1).
This part doesn't apply to you at present, but you also need to watch out if you use a hidden shared actions note, as described here: http://www-10.lotus.com/ldd/bpmpblog.nsf/dx/shared-actions-and-hidden-designs?opendocument&comments
Andre Guirard | 12 November 2009 05:42:52 PM ET | Man-Cave, Plymouth, MN, USA | Comments (6) | Permanent Link

