Subject: RE: Multiple file attachment
The formatting (which is largely destroyed) and naming conventions are my own so if anyone disagrees with them, please blame only me. The comments are a mix of mine and Stickman’s.Also, I only have to support IE, so I may have “broken” some of Stickman’s code.
This code is on a Page element named “MultiSelector.js”. Pay attention to the i__FileInputsMaximum variable.
/* *********************************************************************** */
/** Convert a single file-input element into a ‘multiple’ input list.
**
** This function defines a class that will be instantiated as a global
** variable on the HTML document. The class wraps a collection of file input
** elements, with the newest one active and earlier ones displayed in a list
** with accompanying delete buttons.
**
** Usage:
** 1. Create a file input element (no name)
** eg.
** 2. Create a DIV for the output to be written to
** eg.
** 3. Instantiate a MultiSelector object, passing in the DIV and an (optional) maximum number of files
** eg. var MS_Attachments = new MultiSelector( document.getElementById( ‘files_list’ ), 3 );
** 4. Add the first element
** eg. MS_Attachments.addElement( document.getElementById( ‘first_file_element’ ) );
** 5. That’s it.
**
** You might (will) want to play around with the addListRow() method to make the output prettier.
**
** You might also want to change the line
** element.name = ‘file_’ + this.count;
** …to a naming convention that makes more sense to you.
**
** Licence:
** Use this however/wherever you like, just don’t blame me if it breaks anything.
**
** Credit:
** If you’re nice, you’ll leave this bit:
**
** Class by Stickman – http://www.the-stickman.com */
/* ----------------------------------------------------------------------- */
function MultiSelector( de_AttachmentTable )
{ this.de_AttachmentTable = de_AttachmentTable // Table that lists attachments
this.de_CurrentFileInput; // Allows us to re-enable if delete returns us below limit
this.s__FileInputName = ''; // 2008-Jan-31 EBM: Have to re-use name generated by Domino Designer
this.i__FileInputsCount = 0; // Count of file-input elements
this.i__FileInputsMaximum = 4; // Maximum file-input elements allowed; -1 for unlimited
// --------------------------------------------------------------------------
// Add a new file-input element.
// --------------------------------------------------------------------------
this.addFileInputElement = function( de_FileInput )
{ var de_NextInput;
// ---------------------------------------------------------------------
// Ensure the new element is a file-input.
// ---------------------------------------------------------------------
if( de_FileInput.tagName.toLowerCase() == 'input' && de_FileInput.type.toLowerCase() == 'file' )
// --------------------------------------------------------------
// Give the file input element a unique ID.
// 2008-Jan-31 EBM: We must use the name generated for the File
// Upload control by Domino Designer *for all subsequent
// file-input elements* or else the subsequent file attachments
// will not be saved. The unique ID must be handled via the “ID”
// attribute.
// --------------------------------------------------------------
{ if( this.s__FileInputName == '' )
this.s__FileInputName = de_FileInput.name;
else
de_FileInput.name = this.s__FileInputName;
de_FileInput.id = 'FileInput_' + (this.i__FileInputsCount < 10 ? '0' : '') + this.i__FileInputsCount;
// --------------------------------------------------------------
// Add a property to the file-input element to store a reference
// to the HTML document’s instance of MultiSelector. This allows
// the event handler we’re going to add to the element to access
// its Attachments table, and its addFileInputElement and
// addListRow functions.
// --------------------------------------------------------------
de_FileInput.MS_Attachments = this;
// --------------------------------------------------------------
// Also add a handler to the file-input element for the onchange
// event. In the handler, ‘this’ refers to the handler’s owner -
// the file-input element.
// The event handler first looks to see if the attachment is
// already in the table, clearing itself if the attachment is
// found.
// --------------------------------------------------------------
de_FileInput.onchange = function()
{ for( var i = 0; i < this.MS_Attachments.de_AttachmentTable.rows.length; i++ )
{ if( this.value.toLowerCase() == this.MS_Attachments.de_AttachmentTable.rows(i).cells(1).innerHTML.toLowerCase() )
{ // Cannot find a way to erase the displayed text.
return;
}
}
// --------------------------------------------------------
// Create the next file-input element and insert it ahead
// of this file-input element (within whatever HTML element
// this file-input element resides).
// --------------------------------------------------------
de_NextInput = document.createElement( 'Input' );
de_NextInput.type = 'file';
de_NextInput.style.fontFamily = 'Arial';
de_NextInput.style.fontSize = '8pt';
this.parentNode.insertBefore( de_NextInput, this );
// --------------------------------------------------------
// Using the file-input element’s reference to the HTML
// document’s instance of MultiSelector, call the method to
// set up and display the next file-input element.
// --------------------------------------------------------
this.MS_Attachments.addFileInputElement( de_NextInput );
// --------------------------------------------------------
// Using the file-input element’s reference to the global
// instance of MultiSelector, “move” the current file-input
// element to the dislay list.
// --------------------------------------------------------
this.MS_Attachments.addListRow( this );
this.style.display = 'none';
}
// --------------------------------------------------------------
// If we’ve decided to limit the number of attachments and have
// reached maximum, disable de_FileInput.
// --------------------------------------------------------------
if( this.i__FileInputsMaximum != -1 && this.i__FileInputsCount >= this.i__FileInputsMaximum )
de_FileInput.disabled = true;
// --------------------------------------------------------------
// File-input element counter (for upper-limit checking only)
// --------------------------------------------------------------
this.i__FileInputsCount++;
// --------------------------------------------------------------
// A reference to the most recent element, should the Delete
// button need to re-enable the element because we’ve returned
// below the limit.
// --------------------------------------------------------------
this.de_CurrentFileInput = de_FileInput;
}
}
// --------------------------------------------------------------------------
// Add a new row to the table of file attachments.
// --------------------------------------------------------------------------
this.addListRow = function( de_FileInput )
{ var de_DeleteIcon;
var de_TableRow;
var de_TableCell;
// --------------------------------------------------------------------
// Create a TR element to display the file-input element.
// --------------------------------------------------------------------
de_TableRow = this.de_AttachmentTable.insertRow();
// --------------------------------------------------------------------
// Create a Delete icon that may be used to delete the attachment. Add
// two properties to the icon to store references to the file-input
// element and the table-row element. This gives us easy access to
// both if we need to delete the attachment.
// --------------------------------------------------------------------
de_DeleteIcon = document.createElement( 'Img' );
de_DeleteIcon.src = '/icons/vwicn038.gif';
de_DeleteIcon.de_FileInput = de_FileInput;
de_DeleteIcon.de_TableRow = de_TableRow;
// --------------------------------------------------------------------
// Also add a handler to the Delete icon for the onclick event. In the
// handler, ‘this’ refers to the handler’s owner - the Delete icon.
// --------------------------------------------------------------------
de_DeleteIcon.onclick = function()
{
// --------------------------------------------------------------
// Remove the file-input element (and any children that we may
// have forgotten about) from the HTML document.
// --------------------------------------------------------------
this.de_FileInput.removeNode( true );
// --------------------------------------------------------------
// Remove the table-row element (and its cells, and their
// contents) from the attachments table.
// --------------------------------------------------------------
this.de_TableRow.removeNode( true );
// --------------------------------------------------------------
// Using the Delete icon’s reference to the file-input element,
// use the file-input element’s reference to the HTML document’s
// instance of MultiSelector to decrement the count of file-input
// elements.
// --------------------------------------------------------------
this.de_FileInput.MS_Attachments.i__FileInputsCount--;
// --------------------------------------------------------------
// Using the property we added when creating the Delete icon,
// retrieve the file-input element. Then use the property we
// added to the file-input element to retrieve the HTML
// document’s instance of MultiSelector and re-enable the current
// file input element (in case it was disabled due to a maximum).
// --------------------------------------------------------------
this.de_FileInput.MS_Attachments.de_CurrentFileInput.disabled = false;
}
// --------------------------------------------------------------------
// Add the Delete icon and the file-input element’s display value to
// the table row.
// --------------------------------------------------------------------
de_TableCell = de_TableRow.insertCell();
de_TableCell.style.width = 20;
de_TableCell.appendChild( de_DeleteIcon );
de_TableCell = de_TableRow.insertCell();
de_TableCell.innerHTML = de_FileInput.value;
}
}
How ever you generate the Head section, you’ll need to include the page:
On your page, add a File Upload control and use the HTML tab of the properties dialog box to give it the id “FileInput_00”.
Below the control, add the following pass-through HTML:
This is where the user can see if they’ve already added a file, or remove a mistake.
Normally, I avoid embedding Javascript on Forms like the plague, but I had to here - I can’t remember what obscure problem it solved. This pass-through HTML is immediately below the HTML Table but you may be able to move the variable declaration to JS Header and the variable initialization to the onload() event (beware: IE replaces onload() code in rare situations).
(Note: the capitalized prefix indicates a global Javascript variable whereas lowercase indicates local variables. The “de_” prefix refers to HTML document elements - usually obtained via document.getElementById(…).)