function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Greg HGreg H 

File Attachment sControl

I need to attach a file to a custom object through an scontrol.  I would like to be able to use code similar to the following but the code displayed here does not work.  Does anyone have any ideas?
 
if (frmFile != "") {
     var _refAttach = new Sforce.Dynabean("Attachment");
     _refAttach.set("Body", frmFile);
     var saveAttachment = sforceClient.Create([_refAttach]);
     alert("Created ID: " + saveAttachment[0].id);
} else {
     alert("Please browse for a file!");
}
Best Answer chosen by Admin (Salesforce Developers) 
Greg HGreg H
The code I used to perform this attachment is very long and specific to a custom object.  To provide the most clarity here I am describing the business need and solution but only showing the parts of the code relevant to the topic of attaching a file.
 

The Business Requirement: Create a place within salesforce.com to store customer reference letters and make them searchable based on zip code, SIC Code and specific Employee Ranges.  Users should be able to add new reference letters but the reference letters should not be made available to others unless the corporate marketing team has approved the letter and verified all the information around the letter.
 
The High-Level Solution: Create a custom object to store the reference letters but hide the object from all users except admins and marketing team.  Create an sControl (available to all users) to display a new window and show summary level details around the approved letters including a link to the actual reference letter file.  I've added a screen shot (with customer info blacked out) so you can see the reference letter search sControl.  Using this same sControl the user can add new reference letter information and attach the reference letter itself to the record.
 
 
 
Issues with the High-Level Solution: Simple is always better so I did not want to make the users access another tab to add a reference letter and attach the file.  They already see too many tabs and I wanted them to welcome this new functionality not get confused by it.  Additionally, all of the work to create the new reference letter record and attach the file had to be done in AJAX - for various reasons I was not allowed to do anything on the server-side.
 
Deeper Details Around the Final Solution: Since I already had an sControl to do the reference letter searching and I had the security on the back-end setup to meet the business need of "don't show us too many more tabs", I needed to find a way to attach the files to the records being added by users without making them jump through too many hoops.  This was the reason for my initial posting on this topic.
 
Anyway, when a user clicks the "Add Letter" button (illustrated above) they get to a screen which will allow them to fill out certain information around the reference letter.  They then save that information.  This actually creates a new record on my custom object so when the record is saved I call back the ID for the new record.
 
Code snippet for save information and return of record ID:
//create Dynabean
var _refLetter = new Sforce.Dynabean("Marketing_Content__c");
//set values
_refLetter.set("Name", frmName);
_refLetter.set("RecordTypeId", frmRecordType);
_refLetter.set("Street__c", frmAddress);
_refLetter.set("City__c", frmCity);
_refLetter.set("State_Province__c", frmState);
_refLetter.set("Zip_Postal_Code__c", frmZip);
//create one new record
var saveResult = sforceClient.Create([_refLetter]);
//pass back the ID for later use
var frmParentID = saveResult[0].id;
 
I then prompt the user to browse for the file to attach to this newly created record.  Some of this code was high-jacked from the standard salesforce.com attachment page (/p/attach/NoteAttach) and altered to suit the needs of the sControl.
 
Code snippet for Browse, save using /p/attach/NoteAttach and then display "Thank You":
//upon submission post form to standard salesforce.com attachment page
<form action=/"/p/attach/NoteAttach\" enctype=\"multipart/form-data\" id=\"editPage\" name=\"editPage\" method=\"POST\">
//cancel url is set to my scontrol
<input type=\"hidden\" name=\"cancelURL\" id=\"cancelURL\" value=\"/servlet/servlet.Integration—lid=01N300000008lN2\">
//parent id is set to newly created record
<input type=\"hidden\" name=\"pid\" id=\"pid\" value=\"\" + frmParentID + \"\">
//return url is set to my scontrol
<input type=\"hidden\" name=\"retURL\" id=\"retURL\" value=\"/servlet/servlet.Integration–lid=01N300000008lN2\">
<input type=\"hidden\" name=\"save_new_url\" id=\"save_new_url\" value=\"/p/attach/NoteAttach˜retURL=%2Fservlet%2Fservlet%2EIntegration%3Flid%3D01N300000008lN2&amp;pid=\" + frmParentID + \"\">
...etc...
<td class=\"labelCol requiredInput last\"><label for=\"file\"><span class=\"requiredMark\">*</span>Select the File</label></td>
<td class=\"data2Col last\" colspan=\"3\" style=\"padding-bottom: 1em\"><div class=\"requiredInput\"><div class=\"requiredBlock\"></div><input type=\"file\" 
title=\"Type the path of the file or click the Browse button to find the file.\" 
id=\"file\" size=\"20\" name=\"file\"></div></td>
...etc...
//browse for file
//the onclick event displays a new window containing the sControl and ultimately the "Thanks" message
//notice that I pass some variables to the sControl via the URL so I can close the parent window and so I know the ID of the record that I want to display in the resulting popup window
<input value=\"Attach File\" class=\"btn\" type=\"submit\" title=\"Attach File (New Window)\" 
onclick=\"javascript:setLastMousePosition(event); window.openPopup('/servlet/servlet.Integration?lid=01N300000008lLu&whattodo=closeparent&parentid=\" + frmParentID + \"',
 'Attachment', 870, 500, 'width=870,height=500,resizable=yes,toolbar=yes,status=yes,scrollbars=yes,menubar=yes,directories=yes,location=no,dependant=no', true);\" name=\"Attach\"></td>
...etc...
</form>
 
Once the user submits the file I post the form to the standard salesforce.com attachment page (as shown in the first line of the code directly above).  I then quickly pop a new window with my original sControl displaying a thank you message and displaying the details they just entered in addition to a link to the newly attached file.  When the sControl window pops open I then close the parent window which is actually salesforce.com/p/attach/NoteAttach.
 
Code snippet to read URL, close parent and query:
//check to see if the page is being passed information from the URL
var URLCloseParent = document.URL.indexOf("whattodo=");
var URLSentID = document.URL.indexOf("parentid=");
//make sure there are no errors
if ((URLCloseParent != -1) && (URLSentID != 1)) {
     var varPassedDir = document.URL.substr(URLCloseParent+9,11);
     var varPassedID = document.URL.substr(URLSentID+9,15);
     //make sure the variable passed is what we need
     if (varPassedDir == "closeparent") {
          //close the /p/attach/NoteAttach page
          opener.close();
          //query for new reference letter details
          var queryResult = sforceClient.query("Select Approved_for_Use__c, ...etc... from Marketing_Content__c WHERE Id='" + varPassedID + "'", layoutResults);
     }
} else {
     alert("Error: Required variables are not present!");
}
 
One flaw with this attachment solution is that it is possible for the parent window to close before the file is attached to the record.  To avoid this you can simply add a timeout prior to the parent window closing or alert the user with a message.  Both of which should create enough time for the file to be attached.  Since my users are only adding one-page PDF files I have not yet had to add the timeout or alert functionality.
 
This "smoke and mirrors" approach to saving the attachment works perfectly for me and I hope that this information helps anyone who has a similar need.
 
Please accept my apologies for such a long post but I thought these details would be necessary for people to understand what I did.  Please feel free to contact me if anyone has further questions or would like additional information.
 
Thanks,
-greg
 
Have further questions?

All Answers

SteveBowerSteveBower
You need to set the ParentID to the id of the object that you are associating this attachment with.

You need to encode the body as Binary 64 and set the length.

Code:  (modified by hand)
try {
    _refAttach.set("ParentId",ID of the object you want to attach this to.);
    _refAttach.set("Name",the name of your file or something you like);
    var b64 = new Sforce.Util.Base64();
    var b64data = b64.encode(frmFile);  // presuming that this is a variable holding the contents of your file. 
    _refAttach.set("Body",b64data);
    _refAttach.set("BodyLength",b64data.length);
    var cr = sforceClient.Create(_refAttach)[0];
    if (Sforce.Util.dltypeof(cr) != "SaveResult" ) {
        alert("Error creating attachment:" + cr);
    }
} catch (e) {
    alert("Exception creating attachment:" + e);
}

Good luck, Steve.

Note, there are bugs in the 3.3 Beta regarding retrieving your data from at attachment or document.  You get different behavior depending on the length of the data you've saved (< or > than 2048 bytes).  Some of my other posts have a simple fix to sforceclient.js which solves them.
Greg HGreg H

Thanks Steve!  I am now able to save an attachment but the actual file is not being attached - only an encoded string.  So when I click on the attachment it only displays characters in the browser (i.e. QzpcRG9jdW1lbnRzIGFuZCBTZXR0aW5nc1xnaGFjaWNcRGVza3RvcFxCb29rMS54bHM=)

I wonder if I am requesting the file from the form incorrectly or if I need to provide an file extension on the name of the attachment when I save it (i.e. .pdf)?

My request from the form looks like:
var frmFile = document.form.file.value;

Thanks for your help,
-greg

SteveBowerSteveBower
Remember that you need to retrieve the CONTENT of the file if you want to upload it as the Body of an Attachment.  You're just setting it to the file name which is what you get back from the <input type="file"> type of DOM element.

That was my comment in the code snippet:   // I presume FrmFile is the CONTENT of your file.

You need some browser specific code to "break through" the browser/filesystem boundary.

http://www.captain.at/ajax-file-upload.php  (for mozilla) gives you an idea of something that you could rip apart to get the content of your file into your client-side script to then send to salesforce through their api.

I'm sure there's something out there for IE as well, but I haven't really needed to do this for any of my projects yet.

One of the other Board contributors posted this awhile ago...
http://forums.sforce.com/sforce/board/message?board.id=ajax_toolkit&message.id=408&query.id=757#M408
but I don't see his code.

Hope this helps, Steve.

P.S. I don't know why you're not seeing the File Name being returned correctly since that's what you are saving as "content".  It seems like it's being encoded twice or not decoded, etc.  Also, the results of attachment_dynabean.get("Body") are different depending on the length of the body you've stored. (unless you used a modified sforceclient.js file or overrode parseVal.)


gsickalgsickal
The problem is you should NOT be encoding the attachment body with base64 when saving to salesforce through an scontrol. Instead you should be converting the binary bytestream content of the file into a character array (char[]) that you actually set as the attachment body. I'm not sure how to do this in Javascript, but I did it using a COM object installed on the client as follows:

try {
// create COM object to get the unencoded string representation of the document
var fn = "C:\yourfile";
var obj = new ActiveXObject("ProgId.ProgId");
var body = obj.FileToString(fn);

// save the attachment
var attachment = new Sforce.Dynabean("Attachment");

attachment.set("ParentId", "YourObjectId");
attachment.set("OwnerId", {!User_ID});
attachment.set("Name", fn);
attachment.set("Body", body);

sforceClient.create([attachment]);
}
catch (ex) {
}

The COM C# code (registered as ProgId.ProgId) I have installed on the client to do the bytestream to char[] looks like this. Using this method may not work for you if you don't want to install client code, but I don't think this is possible in Javascript since Javascript does not support the byte[] object type:

public static String FileToString(string filename) {
try {
FileInfo fi = new FileInfo(filename);
if (!File.Exists(fi.FullName)) return "";

//create temp copy of file
string fn = fi.Name;
string fnDir = fi.DirectoryName;
string fnExt = fi.Extension;
string fnPre = fn.Substring(0, fn.Length - 4);
string fnTmp = fnDir + "\\" + fnPre + "_encoded" + fnExt;

//copy to temp file
FileInfo fiTmp = fi.CopyTo(fnTmp, true);
FileStream fs = new FileStream(fiTmp.FullName, FileMode.Open, FileAccess.ReadWrite, FileShare.None);

//read file
byte[] data = new byte[fs.Length];
int nBytesRead = fs.Read(data, 0, (int)fs.Length);

// convert byte[] to char[]
char[] dataChars = new char[fs.Length];
for (int i = 0; i < fs.Length; i++) {
dataChars[i] = (char)(data[i]);
}

if (fs != null) fs.Close();
fiTmp.Delete();

//convert char[] to string
StringBuilder sb = new StringBuilder();
sb.Append(dataChars);
return sb.ToString();
}
catch (Exception ex) {
System.Windows.Forms.MessageBox.Show(ex.Message, "FileToString(FileInfo fi)",
System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Stop);
return "";
}
Greg HGreg H

I was able to resolve this problem. Since I had to attach the file using an sControl and I couldn't perform anything on the server-side, I decided to highjack the "add attachments" page (/p/attach/NoteAttach) and redirect the user to a success page (sControl) after the attachment was saved. It was a bit of a pain to figure out but the functionality is ideal for my users. Thanks to all of you for providing feedback on this.

-greg

SteveBowerSteveBower
Sounds like a neat solution.  Can you post the snippet of code that you created to do this?

It's a nice thing to have "in the library".

Thanks, Steve.

Greg HGreg H
The code I used to perform this attachment is very long and specific to a custom object.  To provide the most clarity here I am describing the business need and solution but only showing the parts of the code relevant to the topic of attaching a file.
 

The Business Requirement: Create a place within salesforce.com to store customer reference letters and make them searchable based on zip code, SIC Code and specific Employee Ranges.  Users should be able to add new reference letters but the reference letters should not be made available to others unless the corporate marketing team has approved the letter and verified all the information around the letter.
 
The High-Level Solution: Create a custom object to store the reference letters but hide the object from all users except admins and marketing team.  Create an sControl (available to all users) to display a new window and show summary level details around the approved letters including a link to the actual reference letter file.  I've added a screen shot (with customer info blacked out) so you can see the reference letter search sControl.  Using this same sControl the user can add new reference letter information and attach the reference letter itself to the record.
 
 
 
Issues with the High-Level Solution: Simple is always better so I did not want to make the users access another tab to add a reference letter and attach the file.  They already see too many tabs and I wanted them to welcome this new functionality not get confused by it.  Additionally, all of the work to create the new reference letter record and attach the file had to be done in AJAX - for various reasons I was not allowed to do anything on the server-side.
 
Deeper Details Around the Final Solution: Since I already had an sControl to do the reference letter searching and I had the security on the back-end setup to meet the business need of "don't show us too many more tabs", I needed to find a way to attach the files to the records being added by users without making them jump through too many hoops.  This was the reason for my initial posting on this topic.
 
Anyway, when a user clicks the "Add Letter" button (illustrated above) they get to a screen which will allow them to fill out certain information around the reference letter.  They then save that information.  This actually creates a new record on my custom object so when the record is saved I call back the ID for the new record.
 
Code snippet for save information and return of record ID:
//create Dynabean
var _refLetter = new Sforce.Dynabean("Marketing_Content__c");
//set values
_refLetter.set("Name", frmName);
_refLetter.set("RecordTypeId", frmRecordType);
_refLetter.set("Street__c", frmAddress);
_refLetter.set("City__c", frmCity);
_refLetter.set("State_Province__c", frmState);
_refLetter.set("Zip_Postal_Code__c", frmZip);
//create one new record
var saveResult = sforceClient.Create([_refLetter]);
//pass back the ID for later use
var frmParentID = saveResult[0].id;
 
I then prompt the user to browse for the file to attach to this newly created record.  Some of this code was high-jacked from the standard salesforce.com attachment page (/p/attach/NoteAttach) and altered to suit the needs of the sControl.
 
Code snippet for Browse, save using /p/attach/NoteAttach and then display "Thank You":
//upon submission post form to standard salesforce.com attachment page
<form action=/"/p/attach/NoteAttach\" enctype=\"multipart/form-data\" id=\"editPage\" name=\"editPage\" method=\"POST\">
//cancel url is set to my scontrol
<input type=\"hidden\" name=\"cancelURL\" id=\"cancelURL\" value=\"/servlet/servlet.Integration—lid=01N300000008lN2\">
//parent id is set to newly created record
<input type=\"hidden\" name=\"pid\" id=\"pid\" value=\"\" + frmParentID + \"\">
//return url is set to my scontrol
<input type=\"hidden\" name=\"retURL\" id=\"retURL\" value=\"/servlet/servlet.Integration–lid=01N300000008lN2\">
<input type=\"hidden\" name=\"save_new_url\" id=\"save_new_url\" value=\"/p/attach/NoteAttach˜retURL=%2Fservlet%2Fservlet%2EIntegration%3Flid%3D01N300000008lN2&amp;pid=\" + frmParentID + \"\">
...etc...
<td class=\"labelCol requiredInput last\"><label for=\"file\"><span class=\"requiredMark\">*</span>Select the File</label></td>
<td class=\"data2Col last\" colspan=\"3\" style=\"padding-bottom: 1em\"><div class=\"requiredInput\"><div class=\"requiredBlock\"></div><input type=\"file\" 
title=\"Type the path of the file or click the Browse button to find the file.\" 
id=\"file\" size=\"20\" name=\"file\"></div></td>
...etc...
//browse for file
//the onclick event displays a new window containing the sControl and ultimately the "Thanks" message
//notice that I pass some variables to the sControl via the URL so I can close the parent window and so I know the ID of the record that I want to display in the resulting popup window
<input value=\"Attach File\" class=\"btn\" type=\"submit\" title=\"Attach File (New Window)\" 
onclick=\"javascript:setLastMousePosition(event); window.openPopup('/servlet/servlet.Integration?lid=01N300000008lLu&whattodo=closeparent&parentid=\" + frmParentID + \"',
 'Attachment', 870, 500, 'width=870,height=500,resizable=yes,toolbar=yes,status=yes,scrollbars=yes,menubar=yes,directories=yes,location=no,dependant=no', true);\" name=\"Attach\"></td>
...etc...
</form>
 
Once the user submits the file I post the form to the standard salesforce.com attachment page (as shown in the first line of the code directly above).  I then quickly pop a new window with my original sControl displaying a thank you message and displaying the details they just entered in addition to a link to the newly attached file.  When the sControl window pops open I then close the parent window which is actually salesforce.com/p/attach/NoteAttach.
 
Code snippet to read URL, close parent and query:
//check to see if the page is being passed information from the URL
var URLCloseParent = document.URL.indexOf("whattodo=");
var URLSentID = document.URL.indexOf("parentid=");
//make sure there are no errors
if ((URLCloseParent != -1) && (URLSentID != 1)) {
     var varPassedDir = document.URL.substr(URLCloseParent+9,11);
     var varPassedID = document.URL.substr(URLSentID+9,15);
     //make sure the variable passed is what we need
     if (varPassedDir == "closeparent") {
          //close the /p/attach/NoteAttach page
          opener.close();
          //query for new reference letter details
          var queryResult = sforceClient.query("Select Approved_for_Use__c, ...etc... from Marketing_Content__c WHERE Id='" + varPassedID + "'", layoutResults);
     }
} else {
     alert("Error: Required variables are not present!");
}
 
One flaw with this attachment solution is that it is possible for the parent window to close before the file is attached to the record.  To avoid this you can simply add a timeout prior to the parent window closing or alert the user with a message.  Both of which should create enough time for the file to be attached.  Since my users are only adding one-page PDF files I have not yet had to add the timeout or alert functionality.
 
This "smoke and mirrors" approach to saving the attachment works perfectly for me and I hope that this information helps anyone who has a similar need.
 
Please accept my apologies for such a long post but I thought these details would be necessary for people to understand what I did.  Please feel free to contact me if anyone has further questions or would like additional information.
 
Thanks,
-greg
 
Have further questions?
This was selected as the best answer
gsickalgsickal
Nice solution and workaround. The thing to note about this solution is that since you are using the salesforce web interface to upload the attachment, the server side code in /p/attach/NoteAttach is actually encoding the attachment into base64 binary behind the scenes (and conversely does base64 decoding when you click on view attachment from within salesforce). Generally speaking you should not explicitly do Base64 encoding when uploading a binary file attachment i.e. word document or pdf file) within an scontrol.
Rohit2006Rohit2006
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>File Upload</title>
<script language="javascript" src="https://www.salesforce.com/services/lib/ajax/beta3.3/sforceclient.js"
type="text/javascript"></script>
<script>
function pgLoad()
{
    try
    {
        sforceClient.init("{!API_Session_ID}", "{!API_Partner_Server_URL_70}", true);
    }
    catch (ex)
    {
        alert(ex.message);
    }
}




function UploadFile()
{
    var _refAttach = new Sforce.Dynabean("Attachment");
    try
    {
    _refAttach.set("ParentId",<ContactId>);
    _refAttach.set("Name","abc.txt");
    var b64 = new Sforce.Util.Base64();
    var b64data = b64.encode(document.getElementById("frmFile").value);  // presuming that this is a variable holding the contents of your file.
    _refAttach.set("Body",b64data);
    _refAttach.set("BodyLength",b64data.length);
    var cr = sforceClient.Create(_refAttach)[0];
    if (Sforce.Util.dltypeof(cr) != "SaveResult" ) {
        alert("Error creating attachment:" + cr);
    }
}
catch (e)
{
    alert("Exception creating attachment:" + e);
}

}
</script>


</head>
<body onload = "pgLoad();">
<input type="file" id="frmFile"/>
<input type="submit" value="Upload" onClick="UploadFile();">
</body>
</html>

This is the code which I am using in SControl (replace <ContactId> with id of actual contact in your account)

The above code saves the file with name abc.txt into Attachments Object and associated with the given contact but when i open this file ,
instead of actual text in the uploaded file I see some text string..
Can you modify and resend me the code?It needs to work on IE and FireFox