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
mikeolmikeol 

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

mikeolmikeol
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<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>
A_SmithA_Smith
I might be missing something, but I think you could use workflow to achieve a similar solution.

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.
Ron HessRon Hess
since this is a custom object, can you turn on field history tracking?
mikeolmikeol
A_Smith is correct.  Workflow could be used as described.  I expect the field update would append using a formula to the log field and then clear out the new-entry field (to ensure it wasn't re-appended when another change was made to the record).  Putting something in the new-entry field would trigger the work flow.  I hadn't used field update before but did today and it's very cool.

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.