Category Archives: visualforce

Rerender custom component after main page inlineEdit Save

Sounds simple. You have a Visualforce (VF) page with apex:detail and a custom component. You want to rerender the custom component after the inlineEdit Save command completes. The VF markup is trivial:

<apex:page standardController="Account">
  <apex:form >
      <c:MyComponent id="theComponent" someArg="false"/>  
      <apex:detail inlineEdit="true" subject="{!Account}" rerender="theComponent"/>
  </apex:form>
</apex:page>

So, this works great except …. if someArg is used to conditionally render a table row or column. Such as in this example:

<apex:component >
    <apex:attribute name="someArg" type="Boolean" required="false" default="false" description="TRUE if component not part of export"/>
    <table>
      <thead>
        <apex:outputPanel rendered="{!NOT(someArg)}">
          <tr id="headerRow">
            <th><apex:outputText value="Column 1 Now: {!NOW()}"/></th>
            <th><apex:outputText value="Column 2 NotSomeArg"/></th>
          </tr> 
        </apex:outputPanel>    
        <apex:outputPanel rendered="{!someArg}">
          <tr>
            <th><apex:outputText value="Column 1 Now: {!NOW()}"/></th>
            <th><apex:outputText value="Column 2 someArg"/></th>    
          </tr>
        </apex:outputPanel>
      </thead>    
    </table>                                                                                           
</apex:component>

The problem
I had a pretty sophisticated component that sometimes rendered 9 columns and sometimes 11 columns in an HTML table, depending on the value of someArg. This works well on initial page load. When the business requirements changed and I needed to rerender the component after inlineEdit save, the rerender action failed. In fact failed hard – leaving blank spaces in the table header and no table rows.

After spending way too much time thinking it was something about inlineEdit and components, other arguments not being passed, or something in the component’s controller, I stumbled upon this sentence in the documentation (something I knew about already but because the component was so sophisticated and was a component, I didn’t think applied).

You cannot use the reRender attribute to update content in a table.

The solution?
No good answers here.

  1. You have to refactor your component so that you either have two components, one for x columns and one for y columns, sharing subcomponents to avoid duplication or …
  2. rethink your design or …
  3. after inlineEdit completes, reload the entire page. oncomplete="location.replace();". I tried this but the user experience was poor, as first inline edit does its Ajax refresh and all looks fine, and then the page reloads. Since this is contrary to the way other pages in Salesforce refresh after inlineEdit Save, I eschewed this option.

Side Note
Here’s a tip when you’re stuck with a complex piece of code that no longer is working for some inexplicable reason: Rebuild the code from its most elementary pieces in your Dev Edition so you have a working version, then add in the suspect bits one at a time until it fails. Try and avoid your custom objects an customizations, reproduce using the OOB Account object or the like. A side benefit if you do this is that if you’re really stuck, you’ll have a short, self-contained, compilable example (SSCCE) – suitable for publishing on a forum for community assistance.

Testing Viewstate in Apex testmethods

If you have a controller plus VF page that could be subject to viewstate issues and you want to avoid introducing viewstate issues once you have already tested some released version of your code, how should you go about doing this?

There is (as of V36.0), no PageReference method getViewStateSize() which would be the obvious choice.

So, is there a way to approximate the viewstate and verify it doesn’t grow as more data volume increases?

First Principles: What is Viewstate comprised of?

Viewstate is used to preserve state across HTTP stateless protocol as the page interacts with the controller via GETs and POSTs (at least until a client-side redirect occurs). From the Salesforce Developer Doc, Viewstate is made up of:

  • All non-transient data members in the associated controller (either standard or custom) and the controller extensions.
  • Objects that are reachable from a non-transient data member in a controller or controller extension.
  • The component tree for that page, which represents the page’s component structure and the associated state, which are the values applied to those components.
  • A small amount of data for Visualforce to do housekeeping.

So, from a testmethod point of view, we have access to the first two: non-transient data members and their reachable objects. The component tree will vary in size depending on how many components are rendered – and this typically varies with the number of list elements in apex:pageBlockTables, apex:dataTables, and apex:repeats. The Visualforce housekeeping space consumption is not available to us.

Steps to take
Assuming we have followed the excellent guidelines in Visualforce in Practice, Chapter 13, Visualforce Performance and Best PracticesRapidfire Rendering of Visualforce Pages, what remains is to use the Apex testmethods as a regression suite to early warn you if you have introduced a viewstate regression, at least from the controller side.

Note, there are ways to avoid Viewstate altogether via client-side techniques such as Javascript Remoting but for purposes of this Blog post, let’s assume you have a traditional VF controller + VF markup.

Here’s a coding approach to use in your testmethod:

@isTest private static void testViewState() {
  // Mock n Sobjects
  MyController ctlr = new Controller();  // same principle applies for extensions
  // exercise setters, getters, and ajax action methods that process all n records 
  Integer ctlrSerializedSize = Json.serialize(ctlr);  // get size of controller, serialized

  // mock another n SObjects so we have 2n total
  ctlr = new Controller();  
  // exercise setters, getters, and ajax action methods that process all 2n records 
  System.assertEquals(ctlrSerializedSize,Json.serialize(ctlr),
                      'non transient size of controller should stay flat');
}

Caveats

  1. For Json.serialize to work, you can’t have any non-transient, unserializable variables like System.SelectOption. Use methods rather than getter properties. A list of restrictions can be found in the Apex Developer Doc – JSON Support. Some things may be hard to work around.
  2. The equality assert given in the code sample most likely works only if the same input conditions are used for the test on n records as is used on 2n records.
  3. I’ll say it again, this won’t test the number of VF apex:components generated by 2n versus n — that could blow up your view state. Hopefully you addressed this the first time with your design by following the aforementioned VF in Practice guidelines, Chapter 13.

Sites Unauthorized error displaying VF page. Why?

One of my users reported getting an ‘Unauthorized Error’ after clicking a button on a Sites Visualforce page. What could be the reason for this?

Well, you might think that the obvious reason would be that the VF page wasn’t included as an allowed page in the Sites Guest user profile. This is easy to check by going to:

Setup | Develop | Sites | mysite | Visualforce Pages

But, the page was enabled. Hmm.

I recreated the error and examined the debug logs. Perhaps the controller was throwing an exception. Remember, you have to monitor the sites guestuser, not yourself, to see the debug logs. But, no controller exception.

I looked through the change history and noted the controller had been updated recently so it must have had something to do with a relatively recent Story.

And the answer was…..

While the controller had been updated (certain getters had their names changed), the VF page hadn’t been updated to reflect the new getter properties. The developer had neglected to test the VF page and had a test been done, the “unauthorized error” would have surfaced and the change wouldn’t have been pushed into production [the developer was me 🙁 ]

Here what was happening:

  1. At run time, clicking the button requested VF to serve up Foo.page.
  2. VF does a runtime compile of the page
  3. The compile fails because the page references getters on the controller that no longer exist
  4. Sites default error handling ensues. In our case, this displays the Out-of-the-box unauthorized page

Visualforce onchange event not working?

Simple post here

I had a VF page where the onchange event within a pageBlockTable column cell wasn’t executing

<apex:column headerValue="some hdr">
  <apex:actionRegion >
    <apex:inputField value="{!r.foo}">
	<apex:actionSupport event="onChange" rerender="thePageBlockTableId"/>
    </apex:inputField>
  </apex:actionRegion>	
   <apex:inputField value="{!r.bar}" rendered="{!r.foo <> 'xyz'}"/>
</apex:column>

When I click on the cell for the foo column and change the inputField, the pageBlockTable does not rerender

Huh?

Problem is that unlike most of Apex and Visualforce, the event attribute values are case sensitive.

Solution?

replace event="onChange" with event="onchange"

Some additional information on case senstivity in Apex and Visualforce can be found here

inputFile body not Transmitted to VF Controller

Here was the use case:

  1. Display step 1 of a VF page with a commandLink to jump to step 2
  2. Step 2 was initially rendered invisible
  3. Step2 included an apex:inputFile component

When Step 2 was rendered, the inputFile component appeared.

But, if you select a file and click a button to upload the file to the controller, the controller does not receive the file body but does receive the filename and content type.

Huh?

The issue appears to be that initially unrendered apex:inputFile components don’t bind as expected to a controller variable declared as transient (as it typically must be to avoid Visualforce viewstate size issues). The upload action method occurs, all other non-transient attributes of inputFile do transmit but the file body does not.

Here’s proof

The controller

public with sharing class Foo {
	// Class to investigate rerender issue with inputFile
	public Boolean				hasStep1Completed		{get {return this.hasStep1Completed == null ? false : this.hasStep1Completed;} set;}						// vis control
	
	public transient Blob  		fileBody				{get; set;}		// must be transient as view state can't handle > 128KB				
	public String       		fileDescription			{get; set;}
	public String  				fileName				{get; set;}		
  	public Integer       		fileSize				{get; set;}
	public String       		fileType				{get; set;}
	
	public Boolean 				isFileBodyNullOnXmit	{get; set;}		// VF feedback for example
	
	public Foo() {}
	
	public PageReference upload() {
		this.isFileBodyNullOnXmit = this.fileBody == null ? true : false;
		return null;
	}
}

This VF page does not work

<apex:page controller="Foo"  tabStyle="Account">
 <apex:form id="form">
  <apex:outputPanel id="step1Op">
   <apex:pageBlock title="Step 1" id="step1Pb"  rendered="{!NOT(hasStep1Completed)}">
	<apex:actionregion >
	 <apex:commandLink value="Go to step2"  reRender="step1Op,step2Op">
		<apex:param name="hasStep1Completed" value="true" assignTo="{!hasStep1Completed}"/>
	 </apex:commandLink>
	</apex:actionregion>
   </apex:pageBlock>
  </apex:outputPanel>

<!--  This doesn't work, initially unrendered inputFile won't transmit file body once rendered -->
  <apex:outputPanel id="step2Op" >
   <apex:pageBlock title="Step2" id="step2Pb" rendered="{!hasStep1Completed}" >
	<apex:pageBlockButtons location="top">
         <apex:commandButton value="Upload" action="{!upload}"/>
	</apex:pageBlockButtons>
	<apex:inputFile value="{!fileBody}" filename="{!fileName}" contentType="{!fileType}" id="file"/>
   </apex:pageBlock>
 </apex:outputPanel>		
  
 <apex:outputPanel id="resultOp" >
   <apex:pageBlock rendered="{!hasStep1Completed}">
	<apex:outputText value="is FileBody Null On Xmit? {!isFileBodyNullOnXmit}"/>
   </apex:pageBlock>
 </apex:outputpanel> 
		
 </apex:form>
</apex:page>

The apex:inputFile is not initially rendered as Step1 hasn’t completed
FileXmitStep1

Now, by clicking the commandLink, I render it and then choose a file and click Upload
FileXmitStep2

The results outputPanel shows that no fileBody was transmitted!
FileXmitResultnullBodyisTrue

So, how to work around this?

The answer was provided in Salesforce StackExchange. Instead of using VF rendered= attributes, the inputFile must be rendered by made invisible by CSS. So, the revised Visualforce page (no changes to the controller)

<apex:page controller="Foo"  tabStyle="Account">
 <apex:form id="form">
  <apex:outputPanel id="step1Op">
   <apex:pageBlock title="Step 1" id="step1Pb"  rendered="{!NOT(hasStep1Completed)}">
	<apex:actionregion >
	 <apex:commandLink value="Go to step2"  reRender="step1Op,step2Op">
		<apex:param name="hasStep1Completed" value="true" assignTo="{!hasStep1Completed}"/>
	 </apex:commandLink>
	</apex:actionregion>
   </apex:pageBlock>
  </apex:outputPanel>

<!--  This does work, initially rendered by css invisible inputFile will transmit file body once made visible --> 
  <apex:outputPanel id="step2Op" style="display: {!IF(hasStep1Completed, 'inline-block', 'none')};">
   <apex:pageBlock title="Step2" id="step2Pb" >
    <apex:pageBlockButtons location="top">
	<apex:commandButton value="Upload" action="{!upload}"/>
    </apex:pageBlockButtons>
    <apex:inputFile value="{!fileBody}" filename="{!fileName}" contentType="{!fileType}" id="file"/>
  </apex:pageBlock>
 </apex:outputPanel>		
  
 <apex:outputPanel id="resultOp" >
   <apex:pageBlock rendered="{!hasStep1Completed}">
	<apex:outputText value="is FileBody Null On Xmit? {!isFileBodyNullOnXmit}"/>
   </apex:pageBlock>
 </apex:outputpanel> 
		
 </apex:form>
</apex:page>

Only the second outputPanel was changed. Note the use of style="display: {!IF(hasStep1Completed, 'inline-block', 'none')};" to trick Visualforce into thinking the inputFile is rendered but using display: none to make invisible.

The sequence of steps now is:
FileXmitStep1
FileXmitStep2

The results outputPanel shows that fileBody was transmitted! Problem solved.
FileXmitResultnullBodyisFalse

Visualforce URLFOR Examples

Running blog post of VF examples for URLFOR that aren’t so obvious. More of an aide-memoire to myself

Clone button

<!-- Note use of URL param clone='1' -->
<apex:commandButton value="Clone" 
   action="{!URLFOR($Action.Foo__c.edit, Foo__c.id, [clone='1'])}" id="clonebtn"/>

Custom Controller – Cancel button – avoid coding a custom Cancel action method

<!-- Goes to value of current page's RetURL parameter if any; if none goes to home page -->
<apex:commandButton value="Cancel" 
                    action="{!URLFOR('/' & $CurrentPage.parameters.retURL)}"/>

Fixed domain name plus merge fields

<!-- Goes to some rest resource on known domain; note use of SFDC formula expression to concatenate-->
<apex:commandButton value="Foo" 
                    action="{!URLFOR('https://www.foo.com/' & someControllerProperty)}"/>

Rerendering Visualforce apex:sectionHeader

You would think you could rerender a sectionHeader component by referring to its id, just like other Visualforce components. Turns out this is not true as of V32

Here is a controller and VF page that tries to rerender an apex:sectionheader by inserting the selectedItem from an apex:selectlist.

public with sharing class FooController {
    // For demonstration of sectionHeader rerender issue
  public List<SelectOption> optionList {
        get { return new List<SelectOption> {
           new SelectOption('option1','option1'),
           new SelectOption('option2','option2')};}
        private set;                                                      
    }
  public String selectedOption {get; set;}
}
<apex:page controller="FooController">
    
    <apex:sectionHeader title="My Title: {!selectedOption}" 
          description="sectionheaderNotInDiv." id="sectionHdrNoDiv"/>
    <apex:outputPanel  layout="block" id="sectionHdrInDiv">
        <apex:sectionHeader title="My Title: {!selectedOption}" 
          description="sectionheaderInDiv."/>	
    </apex:outputPanel>
    <apex:form>
    	<apex:pageBlock>
            <apex:selectList label="choose something" 
                             value="{!selectedOption}" size="1">
               <apex:actionSupport event="onchange" 
                   rerender="sectionHdrNoDiv,sectionHdrInDiv"/>
               <apex:selectOptions value="{!optionList}"/>
            </apex:selectList>
        </apex:pageBlock>
    </apex:form>
    
</apex:page>

Initial page state
sectionHdrRerender1

Choose an option so onchange event occurs and rerender ensues
sectionHdrRender2

And the result
Only the sectionheader inside a div is rerendered even though both ids are specified in the rerender
sectionHdrRerender3

Analysis
Using Chrome Developer Tools, I inspected the HTML for the first apex:sectionHeader.

<div class="bPageTitle">
 <div class="ptBody secondaryPalette brandSecondaryBrd">
  <div class="content">
   <img src="/s.gif" class="pageTitleIcon" title="Home" alt="Home">
   <h1 class="pageType noSecondHeader">My Title: </h1>
   <div class="blank"></div>
  </div>
 </div>
</div>

And, what do you know, despite specifying an id on the first apex:sectionHeader, SFDC doesn’t add it to any generated HTML. No wonder it doesn’t rerender!

Versioning woes – ApexPages.addMessage

I recently updated my Util class from V26 to V31. Unfortunately, this broke some VF page test methods. After some experimentation, I traced the problem down to this:

The ApexPages.addMessage() method has several instantiation forms. The one I was using is:

public Message(ApexPages.Severity severity, String summary, String detail)

Using a pattern like this in some of my VF controllers that did some substantial DML:

public PageReference fooActionMeth() {
  String step;
  try {
    step = '01 step';
    // do some work 'A' using various Util methods that can throw exceptions
    // do some other work 'B' using various Util methods that can throw exceptions
    step = '02 step';
    // do some more work using various Util methods that can throw exceptions
  }
  catch (exception e) {
   ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR,
                                              'some error at step:'+step,
                                              e.getMessage()));
  }
}

My testmethod was structured as shown below. I like this pattern because I can test all the error conditions (usually) in sequence within one testmethod rather than going through the overhead of multiple testmethods, one per error case.

 // ...
 Test.startTest();
 Test.setCurrentPage(Foo.page);
 PageReference resPg;
 // set up conditions to test step 1 and verify exception for work 'A' occurs
 resPg = fooAction();
 System.assert(getMostRecentMsg().contains('expected error code'));
 // set up conditions to test step 1 and verify exception for work 'B' occurs
 resPg = fooAction();
 System.assert(getMostRecentMsg().contains('expected error code')); // fails at V31
 // set up conditions to test step 2 and verify exception occurs
 resPg = fooAction();
 System.assert(getMostRecentMsg().contains('expected error code'));
  // ...
 
  // utility method
  private static String getMostRecentMsg() {
     return ApexPages.hasMessages() 
	? ApexPages.getMessages()[ApexPages.getMessages().size()-1].getSummary() + 
          ' ' + 
          ApexPages.getMessages()[ApexPages.getMessages().size()-1].getDetail() 
	: null;
   }

But, when I upgraded the version of the Util worker code to V31 from V26, the testmethod stopped working for the second assert. Why?

To resolve, I broke the problem down to its essential elements using a separate test class:

@isTest
private static void testAddVFMsg() {    
    Test.startTest();
    Test.setCurrentPage(Page.Foo);
    ApexPages.addMessage(
      new ApexPages.Message(ApexPages.Severity.INFO,'MsgSummary','00detail'));
    System.assertEquals(1,ApexPages.getMessages().size());

    ApexPages.addMessage(
      new ApexPages.Message(ApexPages.Severity.INFO,'MsgSummary','01detail'));
     // passes at V28 and earlier, fails at V29+
    System.assertEquals(2,ApexPages.getMessages().size()); 

    Test.stopTest();
}

Note the arguments to the ApexPages.Message constructor between the two calls. Same summary (second argument), different detail (third argument). I then tried it at V31, V30, …until it passed.

Conclusion
ApexPages.addMessage() behavior varies by SFDC version if the i+1st message has the same summary as the ith message, even if the detail argument is different between i+1st and ith message.

If the summary values are the same, the i+1st message won’t get added to the page messages. This change happened as of V29.

Seems to me that if you call addMessage(..), the resulting getMessages() should include the message you added

Adventures in URL Hacking ‘Send an Email’ on Cases

URL hacking the SFDC Send an Email page is well-known. See for example:

Adventure starts
In one of my orgs, we were using On Demand Email-to-Case. If you’ve used this, you know that SFDC creates a thread_id value based on the orgId and case Id wrapped in special keywords (e.g. ref:_00DK0AaiLo._500K05CUEs:ref) . Any subsequent email sent from the Case will include this thread_id in the body and subject line so that if there is an email reply to your On Demand email-to-case email address, the reply is attached to the originating case (and doesn’t create a new case).

I had a custom VF page associated to the Case object and had added a custom button to send an email. Here was the URL hack (line breaks added for clarity):

<apex:commandButton 
  action="{!URLFOR($Action.Activity.SendEmail,csW.cs.id,
  [p2_lkid=csW.cs.contactId,
  rtype=LPAD('3',3,'00'),
  p3_lkid=csW.cs.id,
  retURL=$CurrentPage.url])}"
 value="Send an Email"/>
  • csW.cs.id refers to a controller variable csW of type CaseWrapper that had a member variable cs of type Case.
  • Per the URL Hack tips, p3_lkid refers to the ID of the target object used for merge fields – here, a Case object. p2_lkid references the recipient of the email – here, a Contact record id.

Adventure becomes a mystery
Given the following sequence:

  1. Customer sends email to Foo-support@ourdomain.com
  2. Customer receives auto-reply email containing the thread_id
  3. From the Case, Support team replies to the original email message (from: foo-support@ourdomain.com)
  4. Customer replies to message in step 3; this reply is properly attached to the Case.
  5. From the Case, Support team sends, using the custom button, a new email messsage to the customer (from: foo-support@ourdomain.com).
  6. Customer replies to message in step 5. This reply creates a brand new case!

It was as if emails generated from the ‘Email Message’ reply link had the proper thread_id but emails generated from the ‘Send and Email’ url hack did not – yet both resolved to the proper SFDC page with all fields filled in correctly.

Adventure Ends
This took me a while to figure out what with the flurry of emails to inspect and that the SFDC page that sends the emails looked just fine.

Here was the solution:

In my URL hack for ‘send an email’, when merge fields are resolved by SFDC, I got this:

https://cs9.salesforce.com/_ui/core/email/author/EmailAuthor
?p2_lkid=003K000000jbawQIAQ
&rtype=003
&p3_lkid=500K0000005CUEsIAO
&retURL=...

Note that &p3_lkid is an 18 character ID of the Case. When the email message was sent, SFDC used this 18 character ID and generated a threadId of ref:_00DK0AaiLo._500K05CUEsIAO:ref.

Contrast this thread_id with the thread_id generated by SFDC when the Case was originally created: ref:_00DK0AaiLo._500K05CUEs:ref, That is:

ref:_00DK0AaiLo._500K05CUEsIAO:ref
ref:_00DK0AaiLo._500K05CUEs:ref

Even though SFDC normally treats 18 character IDs the same as 15 character IDs, if the value of the URL hack field p3_lkid=csW.cs.id is an 18 character ID, the Send an Email SFDC code (not mine) creates a thread_id that is 3 characters longer than the thread_id normally generated when the Case was created via On Demand Email-to-Case

Obviously, the problem is that the ‘Send an Email’ code expected the value of &p3_lkid=500K0000005CUEsIAO to be a 15 character ID, not an 18 character Id, that is: &p3_lkid=500K0000005CUEs.
In other words, the SFDC Send an Email code is not independent of p3_lkId length (15 versus 18). Compounding the mystery was that Email To Case parses thread_ids in such a way as to ignore the 15 vs 18 character ID origin.

Thus, the solution was to change the URL Hack to the following (by substringing the 18 character Case Id to a 15 character value):

<apex:commandButton 
  action="{!URLFOR($Action.Activity.SendEmail,csW.cs.id,
  [p2_lkid=csW.cs.contactId,
  rtype=LPAD('3',3,'00'),
  p3_lkid=LEFT(csW.cs.id,15),
  retURL=$CurrentPage.url])}"
 value="Send an Email"/>

Soft alerts

In several projects I have done, the users don’t want to be bothered with required fields or validation errors on Accounts or Opportunities until they get closer to quotation or order fulfillment. Yet marketing and management reports frequently want ‘categorization’ or ‘taxonomic’ fields filled in (but can live with them blank).

To resolve this in a proactive way and still use SFDC out-of-box page layouts – no VF custom controller or controller extension , I use soft alerts on Account and Opportunity that warn the user that data is not complete and that it should be completed –but it is not urgent that it is done so right this minute.

Here is an example Foo__c object with a soft alert when the Name field contains a ‘colorful’ word
Foo_fubar

This is done with a formula field that leverages the IMAGE() function as shown here:

/* Form of an alert is a set of concatenated strings as follows:

IF(<condition 1>,
IMAGE("/img/samples/flag_yellow|red|green.gif" , 'Yellow|Red|Green' ,12,12) & ' alert text with leading space' & BR() ,
NULL
) &
next condition as above for second alert */


IF (CONTAINS(Name, 'fubar') ,
IMAGE("/img/samples/flag_red.gif" , 'Red' ,12,12) & ' Name contains a colorful slang word' & BR(),
NULL)
&
IF (CONTAINS(Name, 'sylvan') ,
IMAGE("/img/samples/flag_green.gif" , 'Green' ,12,12) & ' Name contains a peaceful word' & BR(),
NULL)

The solution has some limitations as the total size of the formula must be under 5000 characters but for a dozen or so possible alerts, you can avoid using a VF controller. Note the second alert that appears if the Foo__c.name field has a nice word as shown here:
foo_sylvan

What if there are no alerts – how to suppress the custom field on the standard page layout?
If there are no alerts, why waste the valuable line to show nothing? Unfortunately, SFDC standard page layouts don’t permit conditional rendering of fields. Now, you need to escape to Visualforce but the goal is to preserve the advantages of your page layout using apex:detail.

We’ll start with a simple VF page (and we assume that inlineEdit is enabled for your users as it is so convenient):

<apex:page standardcontroller="Foo__c" >
	<apex:pageMessages id="pageMsgs" showDetail="true"/>
	<apex:detail inlineEdit="true" relatedList="true" showChatter="false" subject="{!Foo__c.id}" />	

</apex:page>

In order to suppress the Alerts field, we’ll need to use jQuery to locate the DOM elements and remove them when the value is null. So, here is the expanded page:

<apex:page standardcontroller="Foo__c" >

<apex:includescript value="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js" />

<script>
		$j = jQuery.noConflict();		//	reassign $ to $j to avoid conflict with VF
		var	fooViewJQApp	= {};       //	define the application
		$j(function() {					// 	jQuery shorthand for $(document).ready(function() ..  Wait until page loads 

			(function(app) {			//	Define anon function
				//	--------------------------
				//	variable definitions
				//	--------------------------
				
				//	--------------------------
				//	init:	do initializations
				//	--------------------------
				app.init	= function() {
					app.bindings();		// establish bindings
					app.hideNullAlerts();  // hide the alerts field on page load if null
				};				
				//	-----------------------------------
				//	bindings:  events to event handlers
				//	-----------------------------------
				app.bindings = function() {
					// set up binding for events
                };	// end all bindings				
				
				//	------------------------------------
				//	hideNullAlerts:	Alerts formula field takes up a row and should be suppressed if null (Caption + field)
				//	------------------------------------
				app.hideNullAlerts = function() {
					$j('.labelCol').each(function() {
						if ($j(this).text() == 'Alerts') {		// found the <td> for the caption
							var $alertVal = $j(this).next();	// get adjacent td for the inlineEdit value
							if ($alertVal.text().length == 0) {	// formula field if no value has length 0
								$j(this).parent().remove();			// locate the parent of the alerts label (a tr) remove the <tr> and everything inside it
								return false;						// breaks out of each loop	as we found the alerts field
							}	
						}
					});
				};

				app.init();			// Anon function invokes its own init function
			}) (fooViewJQApp);		// Execute anon function passing in an object representing our application
		});			
	</script>


	<apex:pageMessages id="pageMsgs" showDetail="true"/>
	<apex:detail inlineEdit="true" relatedList="true" showChatter="false" subject="{!Foo__c.id}"  
                     oncomplete="fooViewJQApp.hideNullAlerts();"/>	

</apex:page>

Now, this deserves some explanation:

  1. The jQuery pattern I borrowed from the kittyDressUp pattern in “jQuery Mobile Web Development Essentials” Chapter 7. It provides a nice place to hang all your functions and provide some order and consistency to using jQuery and javascript on your VF page. When you inspect a VF page in Eclipse, it is jarring to see VF markup and javascript interspersed and it can make the page harder to read as there are two different technologies at play working on a common DOM model. I didn’t need to set up any bindings but I left in the placeholder for other future jQuery uses.
  2. When the page loads, the jQuery app’s hideNullAlerts() function is invoked. When the page partial refreshes on an inlineEdit save, the apex:detail oncomplete event is invoked by VF and that too, calls hideNullAlerts().
  3. Using standard page layouts, there is no obvious id field to look for to find the DOM element corresponding to the ‘Alerts’ caption and alerts content field. You could use the SFDC-generated ID but I hate hardcoding ids in anything. Firebug or its equivalent is your friend here to understand the SFDC VF DOM structure for a standard page layout (see next figure). The $j('.labelCol') locates all elements on the page which are captions. Then, each() is used to go through every caption looking for the Alerts caption. Upon locating it (a td tag), the function then looks for the adjacent next sibling (also a td) and tests to see if the result of text() is of length 0. Note that jQuery text() returns the combined contents of the selected element and all descendents. If so, then the alert is null. So, go up one level to the tr and remove it. Since there is only one alert field to remove, stop the each() loop by returning false.

  4. Here is an excerpt of the HTML generated by VF for a standard pagelayout that has inlineEdit enabled. As always, when messing about with the SFDC-generated HTML, you are subject to changes in future releases.

    <table class="detailList" cellspacing="0" cellpadding="0" border="0">
    <tbody>
    <tr>
      <td class="labelCol">Alerts</td>
      <td id="00NK0000001OD2cj_id0_j_id28_ilecell" class="data2Col inlineEditLock" onmouseover="if (window.sfdcPage && window.sfdcPage.hasRun) sfdcPage.mouseOverField(event, this);" onmouseout="if (window.sfdcPage && window.sfdcPage.hasRun) sfdcPage.mouseOutField(event, this);" onfocus="if (window.sfdcPage && window.sfdcPage.hasRun) sfdcPage.mouseOverField(event, this);" ondblclick="if (window.sfdcPage && window.sfdcPage.hasRun) sfdcPage.dblClickField(event, this);" onclick="if (window.sfdcPage && window.sfdcPage.hasRun) sfdcPage.clickField(event, this);" onblur="if (window.sfdcPage && window.sfdcPage.hasRun) sfdcPage.mouseOutField(event, this);" colspan="3">
        <div id="00NK0000001OD2cj_id0_j_id28_ileinner">
         <img width="12" border="0" height="12" alt="Green" src="/img/samples/flag_green.gif">
    Name contains a peaceful word
        <br>
        </div>
      </td>
    </tr>
    <tr>
    <tr>
    <tr>
    </tbody>
    </table>