Link to home
Start Free TrialLog in
Avatar of scribla
scriblaFlag for United Kingdom of Great Britain and Northern Ireland

asked on

View formula: Attachment names, only possible per document or possible per field?

This is my current formula, however I looking to see if I can get a result on specific fields rather than per document.
Either that or check to see if a rich text field (that would store the attachment) is null or not?


@If(@Contains(@AttachmentNames;".doc") & @Contains(@AttachmentNames;".xls");"1. DOC & XLS attached";
@Contains(@AttachmentNames;".DOC") & @Contains(@AttachmentNames;".XLS");"1. DOC & XLS attached";
@Contains(@AttachmentNames;".doc") & @Contains(@AttachmentNames;".XLS");"1. DOC & XLS attached";
@Contains(@AttachmentNames;".DOC") & @Contains(@AttachmentNames;".xls");"1. DOC & XLS attached";
 
@Contains(@AttachmentNames;".doc") ;"2. DOC Attached";
@Contains(@AttachmentNames;".DOC") ;"2. DOC Attached";
 
@Contains(@AttachmentNames;".xls") ;"3. XLS Attached";
@Contains(@AttachmentNames;".XLS") ;"3. XLS Attached";
 
"4. No Attachments")

Open in new window

Avatar of Bill-Hanson
Bill-Hanson
Flag of United States of America image

You can't check which field contains an attachment with @Formula language.

I can help clean up your current formula, though.  This is faster, more accurate,  and does the same thing:
att_names := @LowerCase(@AttachmentNames);
@If(
	@Ends(att_names; ".doc") & @Ends(att_names;".xls");
		"1. DOC & XLS Attached";
	@Ends(att_names; ".doc");
		"2. DOC Attached";
	@Ends(att_names;".xls");
		"3. XLS Attached";
"4. No Attachments")

Open in new window

Avatar of scribla

ASKER

Thanks for that, I did that quite sometime ago and didn't consider using a varible, nor @lowercase!

Is there anyway I could do this? using LS or perhaps during field validation on the form I could report the contents (even if its just evaluted against null) in a hidden field I can then reference in my view formula?
ASKER CERTIFIED SOLUTION
Avatar of Bill-Hanson
Bill-Hanson
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of qwaletee
qwaletee

Bill is the file attachment maniac :)
Thanks!   :)

It's one of my pet peaves, and one of the things that really confused me starting out with Notes.
Avatar of scribla

ASKER

Thanks for the response!

Perhaps this would be simpler if I explained what I was trying to achieve?

I'm not really interested in what the files are called, or the extention. My old formula was the way it was because I felt it was my only option at the time. I am more interested into which RT field that the file has been attached into.

So if it make things any easier, I would be looking to update the hidden text field valves with a simple 0/1 or yes/no if the attachement existed in the relevant field or not. I'm looking to do this for 3 RT fields, however checking for the file extention would be a neat way of trying to prevent users from attaching the wrong doc type in the wrong field.
I'm hoping to check for this in 3 RT fields: Quotes (.doc) CSS (.xls) and Order Acknowledgement (.doc)

Failing that I could ask users to stick to a strict filenaming covention, that way I would not need to check the existance per RT field, I could just get the attachment names and catagorise those in the view.
That is exactly what my example does.  The only difference is that I checked 2 fields where you have 3, and the names of the fields are different.

In my example, I wrote to a single hidden field (FileDisplay) which contained one of the three strings from your question.

It should be very simple to take my example and modify it slightly to meet your needs.

What part don't you understand?
Avatar of scribla

ASKER

Very sorry, I havent actually tried your example yet, as I am at home and don't have designer here.
After speed reading your post on a blackberry device, I came to the conclusion that your example did something different so, hence my post. Having read it again in a proper browser, it is much clearer!

Many thanks, I look forward to trying out your example in the morning.
Avatar of scribla

ASKER

I am having a little trouble with the DocGetFileLocations function, I have never defined a function before in an application. Where do I put your code? I've looked at the Designer help, and I didn't really shed much light on the subject.

Thanks
The best place for this code is in a script library so that you can reuse it.

To create a script library:

1. In the Domino Designer, open your database and goto "Shared Code \ Script Libraries".
2. From the "Script Libraries" view, click on the "New LotusScript Library" action.
3. Once the new library is open, you may see something like this:

Option Public
Option Declare

4. Remove the text in this window, and paste ALL of the code below (don't include the line numbers).
5. Save the library with a name you will remember (like "common_functions").

Now that we have a library, we can use it anywhere that LotusScript is allowed.

To use the library in your form:

1. Open your form in the Designer.
2. In the form's "Objects" window, locate the "(Options)" event, and make sure it looks like this:

Option Declare
Use "common_functions"

3. Save your form to make sure that the library is included properly.  If there are any problems, the compiler will let you know (let me know too).
4. OK, now we have everything we need.  The only thing left is to make use of the library by adding the PostSave code.
5. In the form's "Objects" window, locate the "Postsave" event, and make sure it looks like this:

Sub Postsave(Source As Notesuidocument)
     
      ' Save file metadata.
      Dim doc As NotesDocument
      Dim fileMap List As Variant
      Dim hasDoc As Boolean
      Dim hasXl As Boolean
      Set doc = Source.Document
      Call DocGetFileLocations(doc, fileMap)
      If (Isempty(fileMap)) Then
            doc.FileDisplay = "4. No Attachments"
      Else
            Forall fileNames In fileMap
                  Select Case Listtag(fileNames)
                  Case "WordDocs"
                        Forall fileName In fileNames
                              If (Right(Lcase(fileName), 4) = ".doc") Then
                                    hasDoc = True
                                    Exit Forall
                              End If
                        End Forall
                  Case "ExcelDocs"
                        Forall fileName In fileNames
                              If (Right(Lcase(fileName), 4) = ".xls") Then
                                    hasXl = True
                                    Exit Forall
                              End If
                        End Forall
                  End Select
            End Forall
            If (hasDoc And hasXl) Then
                  doc.FileDisplay = "1. DOC & XLS Attached"
            Elseif (hasDoc) Then
                  doc.FileDisplay = "2. DOC Attached"
            Elseif (hasXl) Then
                  doc.FileDisplay = "3. XLS Attached"
            Else
                  doc.FileDisplay = "4. No Attachments"
            End If
      End If
      Call doc.Save(True, False)
     
End Sub

6. That's it!

Here's what the Postsave code does.

First, DocGetFileLocations determines where all files are and builds a HashTable keyed by field name.  If any V2 style attachments are found, it lists them under the "$DOCUMENT" key since they are not in any field.

Next, we check the HashTable (called fileMap) to see what was found.  If fileMap is empty, there are no attachments; otherwise, we loop through the entries.  In the Forall loop, "Listtag(fileNames)" is the name of the current field, and fileNames is an array of file names found in that field.  The existence of a field name in the hashtable is enough to verify that there is a file attachment in that field, but this example goes one step further and checks the name of each file in each field.

I hope this helps.
Option Public
Option Declare
 
%INCLUDE "lsconst.lss"
 
Sub DocGetFileLocations(doc As NotesDocument, fileMap List As Variant)
	
	'/**
	' * Locates all file attachments in a document and returns the result in a hash table (list).
	' * @param doc The document that contains the attachments.
	' * @param fileList (Return) A hash table (list) used to store and return the results of this function.
	' */
	
	Dim allFiles As Variant ' all files embedded within the specified document regardless of which (if any) richtext field contains them.
	Dim rtFiles As Variant ' files found embedded within any rich text field.
	Erase fileMap ' clear the return param.
	
	' Get all attachment names.
	allFiles = ArrayTrimArray(Evaluate("@AttachmentNames", doc))
	If (Isempty(allFiles)) Then Exit Sub
	
	' Check all richtext items for embedded files.
	Forall item In doc.Items
		If (item.Type = RICHTEXT) Then
			If (Not Isempty(item.EmbeddedObjects)) Then
				Forall obj In item.EmbeddedObjects
					fileMap(item.Name) = ArrayAdd(fileMap(item.Name), obj.Name)
					rtFiles = ArrayAdd(rtFiles, obj.Name)
				End Forall
			End If
		End If
	End Forall
	
	' Get the files that are not embedded within an item.
	Forall file In allFiles
		If (Not ArrayIsMember(rtFiles, file, False)) Then
			fileMap("$DOCUMENT") = ArrayAdd(fileMap("$DOCUMENT"), file)
		End If
	End Forall
	
End Sub
 
Function ArrayAdd(source As Variant, values As Variant) As Variant
	
	'/**
	' * Appends one or more element to an array and returns the result as a third array.
	' * Unlike ArrayAppend, this function supports scalar, null, and byte array arguments.
	' * @param source The source array.
	' * @param values The value or values to append to the array.
	' * @return A new array containing all elements from <i>source</i> and <i>values</i>.
	' */
	
	Dim ta1 As Variant ' temp array
	Dim ta2 As Variant ' temp array
	' Check for empty arrays.
	If (ArrayElements(values) = 0) Then
		Redim ta1(0)
		If (Isobject(source)) Then
			Set ta1(0) = source
		Elseif (Isarray(source)) Then
			ta1 = source
		Else
			ta1(0) = source
		End If
		ArrayAdd = ta1
		Exit Function
	End If
	If (ArrayElements(source) = 0) Then
		Redim ta2(0)
		If (Isobject(values)) Then
			Set ta2(0) = values
		Elseif (Isarray(values)) Then
			ta2 = values
		Else
			ta2(0) = values
		End If
		ArrayAdd = ta2
		Exit Function
	End If
	' Check for scalar values and objects.
	If (Isarray(source)) Then
		ta1 = source
	Else
		Dim td1 As Variant ' temp data
		If (Isobject(source)) Then Set td1= source Else td1= source
		Redim ta1(0)
		If (Isobject(td1)) Then Set ta1(0) = td1 Else ta1(0) = td1
	End If
	If (Isarray(values)) Then
		ta2 = values
	Else
		Dim td2 As Variant ' temp data
		If (Isobject(values)) Then Set td2= values Else td2= values
		Redim ta2(0)
		If (Isobject(td2)) Then Set ta2(0) = td2 Else ta2(0) = td2
	End If
	' Check for byte arrays - Arrayappend throws Type Mismatch on Byte arrays in R7.
	If ((Typename(ta1(0)) = "BYTE") Or (Typename(ta2(0)) = "BYTE")) Then
		ArrayAdd = ArrayAppendEx(ta1, ta2)
	Else
		ArrayAdd = Arrayappend(ta1, ta2)
	End If
	
End Function
 
Function ArrayAppendEx(a1 As Variant, a2 As Variant) As Variant
	'/**
	' * Appends one array to the end of another array and returns the result as a third array.
	' * Unlike ArrayAppend, this function supports byte arrays.
	' * @param a1 Any variant containing an array.
	' * @param a2 Any variant containing an array.
	' * @return A variant containing an array.
	' */
	Dim retval As Variant
	Dim start As Long
	Dim i As Long
	retval = a1
	start = Ubound(a1) + 1
	Redim Preserve retval(start + Ubound(a2))
	For i = 0 To Ubound(a2)
		retval(start+i) = a2(i)
	Next
	ArrayAppendEx = retval
End Function
 
Function ArrayElements(source As Variant) As Long
	'/**
	' * Determines the number of elements in an array.
	' * @param source The array to check.
	' */
	Select Case Datatype(source)
	Case V_EMPTY, V_NULL
		ArrayElements = 0
		Exit Function
	Case V_INTEGER, V_LONG, V_SINGLE, V_DOUBLE, V_CURRENCY, V_DATE, V_STRING, V_LSOBJ, V_PRODOBJ
		ArrayElements = 1
		Exit Function
	Case V_BYTE, V_BOOLEAN
		ArrayElements = 1
		Exit Function
	Case Else
		If Isempty(source) Then
			ArrayElements = 0
			Exit Function
		Else
			ArrayElements = Ubound(source) - Lbound(source) + 1
		End If
		If (ArrayElements = 1) Then
			Select Case Datatype(source(Lbound(source)))
			Case V_EMPTY, V_NULL
				ArrayElements = 0
			Case V_LSOBJ, V_PRODOBJ
				If (source(Lbound(source)) Is Nothing) Then ArrayElements = 0
			End Select
		End If
	End Select
End Function
 
Function ArrayTrimArray(source As Variant) As Variant
	
	'/**
	' * Removes all empty elements from an array.
	' * @param source The source array.
	' * @return A new array that has all empty elements removed.
	' */
	
	Dim a As Variant
	If (Not Isarray(source)) Then
		ArrayTrimArray = source
		Exit Function
	End If
	Forall value In source
		If (Datatype(value) = V_STRING) Then
			If (Trim(value) <> "") Then a = ArrayAdd(a, value)
		Elseif (Not Isempty(value)) Then
			a = ArrayAdd(a, value)
		End If
	End Forall
	ArrayTrimArray = a
	
End Function
 
Function ArrayIsMember(source As Variant, values As Variant, Byval caseSensitive As Boolean) As Boolean
	
	'/**
	' * Searches an array for an exact value or values.
	' * @param source The source array.
	' * @param values The value(s) to search for.  This can be a scalar, a string, or an array or values.
	' * @param caseSensitive (Boolean) Indicates whether string matching should be case sensitive.
	' * @return (Boolean) True if an exact value was found.
	' */
	
	Dim a1 As Variant
	Dim a2 As Variant
	Dim comp As Integer
	ArrayIsMember = True
	a1 = ArrayCreate(source)
	a2 = ArrayCreate(values)
	If (caseSensitive) Then comp = 0 Else comp = 1
	' Search for any value in a2 that is present in a1.
	Forall value1 In a1
		Forall value2 In a2
			If ((Datatype(value1) = V_STRING) And (Datatype(value2) = V_STRING)) Then
				If (Strcompare(value1, value2, comp) = 0) Then Exit Function
			Else
				If (value1 = value2) Then Exit Function
			End If
		End Forall
	End Forall
	ArrayIsMember = False
	
End Function
 
Function ArrayCreate(source As Variant) As Variant
	
	'/**
	' * Creates an array from the source.
	' * @param source An array or string containing a list of array elements separated by a comma.
	' * @return A new array containing the elements found in the source.
	' */
	
	Dim result(0) As Variant
	If (Isarray(source)) Then
		ArrayCreate = source
	Elseif (Isobject(source)) Then
		Set result(0) = source
		ArrayCreate = result
	Elseif (Instr(1, source, ",", 0) <> 0) Then
		ArrayCreate = Split(source, ",")
	Else
		result(0) = source
		ArrayCreate = result
	End If
	
End Function

Open in new window

Avatar of scribla

ASKER

Thanks for such a full explaination. Sorry for the slow response, I'm being pulled in all directions here.
I'll knuckle down and give it go on monday. Have a good weekend.
No problem.  Let me know if you have any questions.  I know this can seem overwhelming if you are not familiar with LotusScript, but it's the only way to do what you need.
Avatar of scribla

ASKER

Thanks Bill,  I had this working in less than 5 minutes!
I am going to go it alone with getting this to work exactly the way I want it (as the scope has changed) and hopefully learn a little LS on the way. Seems only fair to wrap this solution up and open another question should I run into trouble.

Thanks for your help and effort, absolutely first class!
Avatar of scribla

ASKER

11/10! Thank you very much for your time, expertise and patience.