You need to sign in to do that
Don't have an account?
An Scontrol and some HTML to append data to a Salesforce field, i.e. making a field a log
Salesforce does not allow us to define fields that can be
used as a chronological log. We have text that can be altered or cleared
and that's it. The following code is my effort at creating a
"logging field". I've not written an scontrol with any more
than alerts and prompts before and discovered quite a few things on the
way. Additionally there are other issues that I've yet to resolve.
I hope this entry is helpful to others and am also hoping some of you can see
better ways to do this.
====================================
In a nutshell what I want to do is to front end the way users can edit a
Salesforce Long Text Area field such that they can only append to the text
already in the field and that users initial and date of update are included
when they do add to the log.
Salesforce allows us to give users field level-security of no access (not
visible), read only access (visible and read-only) or update access (visible
and not read-only) to a field. Page Layouts allow us to reduce this
through the Salesforce front end. Someone with update access as defined
in field level-security may have no access through the page layout if the field
is not available. They can still update the field though other means such
as API and Ron Hess' SF Connector is an easy example.
The code below manages access via page layouts only. So bear in mind that
users who you want to append info to the field will have the ability to alter
or clear the field through the API. Luckily most users are content to
only use Salesforce via the page layout interface.
To create an "append only" field users are given update access to the
field, read-only access in their page layout and a button on the page-layout
which will drive an Scontrol which will allow them to make an entry which the
Scontrol appends to the field. The Scontrol actually puts the User_Alias
(in our site we use this to set the user's initials) and the date in front of
the appended text entered by the user and then stuffs all this into the front
of the existing field contents. This gives a reverse chronological log,
where the most recent entry is at the top.
Some experiences I learnt on the way. Some fixed, some remaining a pain
for which I have no answers:
1. IE 7.0 users must enable native XMLHTTP. Under "Help &
Training" search for "What Internet
Explorer browser settings are optimum for salesforce.com?" to get the full
list of things that must be set to make IE work.
2.Firefox prior to 2.0 generates a console error on
textareas if you use alerts for debugging. Because it's on the console
users won't see it. Refer notes at lines 213-215
3. I gather you load a JavaScript variable to a HTML
page using document.write (line 233). The fact that all carriage
return/line fees are stripped when you do this was a bit of surprise. So
in line 232 they are all changed to HTML <BR /> tags.
4. As this code is a single field update it made more
sense to do this as a small pop-up where the user would launch the scontrol to
do the append, add their note and exit. The underlying page would
typically be the full record display. I gather as salesforce.com launches
the S-Control this restricts the S-Controls ability to control the
window. (If the code was launched from a web link, you can define some
things like screen size. But if you call the code from a Hypertext you
can't do this).
4.1 In IE you get a pop-up size screen. In
Firefox you get either a full screen or a new tab. I want a pop-up size
in both browsers. Salesforce doesn't allow me to define this as part of a
Hypertext launch so I resize once running. Lines 99-115 make
the screen max, position towards the middle and shrink it back. Seems a
bit of a kludge but gets the job done. Firefox alas, positions in the top
left corner regardless. In Firefox options, you must set "New pages
should be opened in a new window" under Tools, Options..., Tabs. IE
6 and earlier don't support tabs. Presumably IE 7 has a similar option of
opening in new window/new tab.
4.2 I'd really rather not have the Menu Bar and
Address Bar(s) showing in the pop up. My understanding is that you can
ask for them not to show if you do the Window.Open. But as Salesforce.com
launches the Window we don't get to choose :(
4.3 When the user hits Save or Cancel the Scontrol
updates or doesn't update the field as requested, but then closes the
pop-up. This works fine in Firefox and IE6. Alas IE 7 asks the user
to confirm the window close. Again, because we didn't open the window I
don't believe there's a way to avoid this.
4.4 No this bit is really interesting. I wanted
to call this S-Control from a hypertext field, so that I could define a button
and position it next to the field displayed on the page-layout. To get
the address of the S-Control I defined a web-link to it so I could start
testing earlier (takes time to define a button) by using the properties right
click and besides I have always started this way. So initially I had the
hypertext working but was a bit disappointed because I was always getting a
display with the left hand frame in it - search, recent items, etc.). Also the pop-up could only be closed by closing the window (red-x). I couldn't programatically close on the user hitting Save or Cancel. Oh
well, something I had to live with. At some stage something broke and
when I hit the button Salesforce said my address was invalid. Strange, I
hadn't changed it. Instead of looking up the address again from the
weblink I used SF Connector to list the S-Controls ID. When I plugged its
address into the Hypertext I got a pop-up without the left hand pane. Even better, window.close() now worked.
Yeah!
I can't fit the above and the code in to a single post to the discussion board so I'll add the code first.
====
Message Edited by mikeol on 03-31-2007 07:42 PM
<link href="/dCSS/Theme2/default/common.css" type="text/css" media="handheld,print,projection,screen,tty,tv" rel="stylesheet" >
<link href="/dCSS/Theme2/default/custom.css" type="text/css" media="handheld,print,projection,screen,tty,tv" rel="stylesheet" >
<html>
<head>
<title><Append Installer Notes V1.00.htm></title>
<script src="https://www.salesforce.com/services/lib/ajax/beta3.3/sforceclient.js" type="text/javascript"></script>
<script language="JavaScript">
function right(str, len)
{
return str.substring(str.length-len,str.length);
}
function SF_Query(SFQ_name, SFQ_Command,SFQ_Response_expected,Fail_on_SQL_Error,Fail_on_no_data)
{
queryResult_SFQ = sforceClient.Query(SFQ_Command);
if (queryResult_SFQ.className != SFQ_Response_expected)
{
if (Fail_on_SQL_Error == true)
{
errormsg = Error(errormsg, "Error: Salesforce " + SFQ_name + " Query failed: " + queryResult_SFQ.className + " (" + SFQ_Response_expected + " expected). Contact Salesforce Administrator.")
errormsg = Error(errormsg, queryResult_SFQ.toString());
exit = true
}
SFQ_Result = ["Error", queryResult_SFQ.className]
}
else
{
if (Fail_on_no_data == true && queryResult_SFQ.records.length == 0)
{
errormsg = Error(errormsg, "Error: Salesforce " + SFQ_name + " Query produced zero results. Contact Salesforce Administrator.")
errormsg = Error(errormsg, queryResult_SFQ.toString());
exit = true
SFQ_Result = ["Error", 0]
}
else
{
SFQ_Result = ["OK", queryResult_SFQ.records.length]
}
}
return(queryResult_SFQ)
}
function Error(errormsgold, errormsgnew, exit)
{
if (errormsgold != "")
{
errormsgold = errormsgold + LF
}
return(errormsgold + errormsgnew)
}
function append()
{
if (Save_Done)
{
alert("You have just appended to this record. Close this window" +
" then refresh the prior screen to see your update.");
}
else
{
Appending_Installer_Notes = document.getElementById('Append_note').value;
if (Appending_Installer_Notes == null || Appending_Installer_Notes == "")
{
alert("A new note must be specified for appending.")
}
else
{
Save_Done = true
Appending_Installer_Notes = Today + " " + UserAlias + ": " + Appending_Installer_Notes
if (Existing_Installer_Notes == "")
{
Revised_Installer_Notes = Appending_Installer_Notes
}
else
{
Revised_Installer_Notes = Appending_Installer_Notes + LF + Existing_Installer_Notes
}
PWO_Record.set("Installer_Notes__c",Revised_Installer_Notes)
SFU_PWO = sforceClient.Update([PWO_Record])[0];
if (SFU_PWO.className != "SaveResult")
{
errormsg = Error(errormsg, "Error: Salesforce SFU_PWO Update failed: " + SFU_PWO.className + " (SaveResult expected). Contact Salesforce Administrator.")
errormsg = Error(errormsg, SFU_PWO.toString());
exit = true
}
window.close();
}
}
}
function initPage()
{
// For Maximise code refer http://www.dynamicdrive.com/dynamicindex8/automax.htm
top.window.moveTo(93,133); // try and centre a bit
if (document.all)
{
top.window.resizeTo(screen.availWidth,screen.availHeight);
}
else
{
if (document.layers||document.getElementById)
{
if (top.window.outerHeight<screen.availHeight||top.window.outerWidth<screen.availWidth)
{
top.window.outerHeight = screen.availHeight;
top.window.outerWidth = screen.availWidth;
}
}
}
top.resizeBy(-100,-200)
document.getElementById('Append_note').focus()
}
function report_error()
{
if (exit == true)
{
if (errormsg != "")
{
alert(errormsg);
}
}
}
function cancel()
{
window.close();
}
</script>
<body class="custom customTab37" onload="javascript:initPage()">
<DIV class=bPageTitle>
<DIV class="ptBody secondaryPalette">
<DIV class=content><IMG class=pageTitleIcon alt=Entity_Name src="/s.gif">
<H1 class=pageType>Append Installer Notes<SPAN class=titleSeparatingColon>:</SPAN></H1>
<H2 class=pageDescription>PWO {!Contract_Sites_Link__c.Name}</H2>
</DIV></DIV></DIV>
<script language="JavaScript">
// ---------------------------------------------------------------------------------------------------------------
// V1.00 - Where it all started
// V1.01
// - Fix for IE 7.0
// ---------------------------------------------------------------------------------------------------------------
errormsg = ""
exit = false
LF = "\n"
Save_Done = false
API_ID = "{!API_Session_ID}"
API_URL = "{!API_Partner_Server_URL_70}"
if (window.XMLHttpRequest)
{
sforceClient.appType = Sforce.Application.Type.FireFox;
}
sforceClient.setLoginUrl("https://www.salesforce.com/services/Soap/u/7.0");
sforceClient.init(API_ID, API_URL, false);
window.setTimeout(";", 1000);
// Get logged on user's UserId record no
if (sforceClient.getSessionId().indexOf("!API_Session_ID") != -1)
{
errormsg = Error(errormsg, "You are not logged in to Salesforce. Please log back in then retry the weblink.")
exit = true
}
if (exit != true)
{
var gui = sforceClient.GetUserInfo();
UserId = gui.userId;
UserAlias = "{!User_Alias}"
// Get data related to the relevant PWO
PWO_Id = "{!Contract_Sites_Link__c.Id}"
Today = new Date();
Today = right("00" + Today.getDate(),2) + "/" + right("00" + (Today.getMonth() + 1),2) + "/" + right("0000" + Today.getFullYear(),4);
}
if (exit != true)
{
SFQ_PWO = SF_Query("PWO", "Select Id, Installer_Notes__c " +
"FROM Contract_Sites_Link__c Where Id = '" + PWO_Id + "'","QueryResult",true,true);
}
if (exit != true)
{
// Select the first PWO record (There's only one per PWO Id)
PWO_Record = SFQ_PWO.records[0]
Existing_Installer_Notes = PWO_Record.get("Installer_Notes__c")
}
</script>
<TABLE cellSpacing=0 cellPadding=0 border=0>
<TBODY>
<TR>
<TD class=pbTitle><IMG class=minWidth title="" height=1 alt="" src="/s.gif" width=1></TD>
<TD class=pbButton>
<INPUT class="btn" title="Save" name="Save" value="Save" autocomplete="off" type="button" onclick="javascript:append()" />
<!-- autocomplete="off" is probably a good idea for this field, however, it has been specified to avoid a known Firefox bug that
generates an error on the JavaScript Console "Permission denied to set property XULElement" when an alert is issued from a text field
refer: http://sillydog.org/forum/sdt_10995.php Note: firefox still performs OK, the error is not presented to the user -->
<INPUT class="btn" title="Cancel" name="Cancel" value="Cancel" autocomplete="off" type="button" onclick="javascript:cancel()" />
</TD>
</TR>
</TBODY>
</TABLE>
<DIV class="bPageBlock bEditBlock secondaryPalette" id=ep>
<DIV class=pbBody>
<DIV class=pbSubsection>
<TABLE class=detailList cellSpacing=0 cellPadding=0 border=0>
<TBODY>
<TR>
<TD class=labelCol>Current Installer Notes</TD>
<TD class="dataCol col02">
<script type="text/javascript">
BR_Existing_Installer_Notes = Existing_Installer_Notes.replace(/\n/g, '<br>'); // \n changed to <br> globally
document.write(BR_Existing_Installer_Notes);
</script>
</TD>
<TR>
<TD class=" requiredInput labelCol"><LABEL for="Append_note">Installer Note to Append</LABEL></TD>
<TD class="dataCol col02">
<DIV class=requiredInput>
<DIV class=requiredBlock></DIV>
<textarea id="Append_note" name="Append_note" rows="10" cols="150"></textarea></div>
</TD>
</TR>
</TBODY>
</TABLE>
</DIV>
</DIV>
</DIV>
<script type="text/javascript">
// Post HTML load script can go here
</script>
</body>
</html>
You could create a workflow rule that's fired everytime the record is edited and add a workflow field update that will copy the text from a field that's editable by the user and append it to the value of another read-only field.
Also, if you need this functionality on custom objects, then field history tracking might be a better solution.
In this instance I wanted a simple pop up that allowed the user to key the info in directly without having to pull up the whole record and navigate to the field in particular. So I still needed to do all the HTML work, but if the front end wasn't necessary I go with work flow in a snap.
Ron is also correct. Alas, custom objects do not support history logging. I know its been on the wish list for a while now. Regardless I wanted a complete log rather than a history of changes which when assemble together would represent the full story.
Thanks for the feedback.