Saturday 25 August 2012

Sobject Preview Panel

Something that I find myself doing at fairly regular intervals is providing selectlists on Visualforce pages to allow a user to choose from a list of records.  Sometimes its because I want to restrict the selection due to complex rules, so a lookup isn't suitable, sometimes I'm not using an inputfield to capture the id, but mostly its because I find it a lot faster to select from a drop down list.

The downside to this is that if the record names are quite similar, it can be difficult for users to pick the right one.  When this is the case, I usually provide a dynamically rendered preview of the record in the page so that the user can check it is the correct record.

Here's a simple example of a dropdown - which of the 'Blog Account' entries is the one I am interested in?


Adding a preview pane shows me the detail of the selected item:



as I actually wanted BrightGen, it is immediately apparent that I've chosen the wrong record (I know the name makes that pretty clear too, but this is only an example :).  I can then choose the correct record and verify that from the details:


The apex controller is very simple - it has a method to retrieve selectoptions for 10 accounts and provides a property to store the user's selection:

public with sharing class DynamicDetail
{
	public String chosenAccountId {get; set;}
	public List<SelectOption> getAccounts()
	{
		List<SelectOption> accOptions=new List<SelectOption>();
		accOptions.add(new SelectOption('Choose', 'Choose'));
		for (Account acc : [select id, Name from Account limit 10])
		{
			accOptions.add(new SelectOption(acc.id, acc.Name));
		}
		
		return accOptions;
	}
}


The page is also pretty simple - a pageblock to choose the account and an actionsupport to rerender the preview panel when a selection is made. Note that I've put the preview pane in the stop facet of an actionstatus. This means that when the user chooses a different account, I don't have to worry about clearing down theuser is entering information that will be presented in a particular context.  For example, entering markup into a rich text field that will appear in a branded Force.com site - having a preview of the content inside the branded page gives you an immediate indicator as to how well the content integrates with the site look and feel.



By the way, if you are seeing odd line numbering on the code samples, this appears to be a bug in chrome.  Hopefully it will be fixed soon.

Saturday 11 August 2012

The Importance of Page Messages

This week's blog is inspired by a lengthy thread that I was involved in on the Visualforce Discussion Board.  This concerned an action method tied to an onchange event not being called.  I was able to track down what the problem was thanks to the <apex:pageMessages/> component, which displays all error messages associated with the page.

Here's an example of how this component can help track down problems when rerendering parts of a page.  The following page simply presents a button to invoke an action method that increments the counter:

<apex:page controller="PageMessages">
  <apex:form >
    <apex:pageBlock title="Page Messages Test 1">
      <apex:pageBlockButtons >
         <apex:commandButton value="Click Me" action="{!click}" rerender="counter"/>
      </apex:pageBlockButtons>
      <apex:outputLabel value="Count  = " />
      <apex:outputText value="{!countVal}" id="counter"/>
    </apex:pageBlock>
  </apex:form>
</apex:page>

The controller contrives to produce an error rather than increment the counter:

public with sharing class PageMessages {
 public Integer countVal {get; set;}
 private Boolean fakeError=true;
 
 public PageMessages()
 {
  countVal=1;
 }
 
 public void click()
 {
  if (fakeError)
  {
   ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, 'Fake Error'));
  }
  else
  {
   countVal++;
  }
 }
}

The page when rendered is as follows:




Opening the page in a browser window: and clicking the 'Click Me' button results in no change to the count value, and as the rerender attribute turns the command into an AJAX request, it looks like clicking the button has no effect whatsoever.  In this case I can turn on debug logging and see that an error message is being added to the page:

14:37:19.021 (21244000)|CODE_UNIT_STARTED|[EXTERNAL]|01p80000000cOqp|PageMessages invoke(click)
14:37:19.021 (21302000)|HEAP_ALLOCATE|[EXTERNAL]|Bytes:6
14:37:19.021 (21322000)|HEAP_ALLOCATE|[EXTERNAL]|Bytes:524
14:37:19.021 (21338000)|METHOD_ENTRY|[1]|01p80000000cOqp|PageMessages.PageMessages()
14:37:19.021 (21345000)|STATEMENT_EXECUTE|[1]
14:37:19.021 (21357000)|SYSTEM_MODE_ENTER|false
14:37:19.021 (21370000)|HEAP_ALLOCATE|[EXTERNAL]|Bytes:5
14:37:19.021 (21376000)|STATEMENT_EXECUTE|[1]
14:37:19.021 (21385000)|SYSTEM_MODE_EXIT|false
14:37:19.021 (21394000)|METHOD_EXIT|[1]|PageMessages
14:37:19.021 (21443000)|SYSTEM_MODE_ENTER|false
14:37:19.021 (21450000)|HEAP_ALLOCATE|[12]|Bytes:5
14:37:19.021 (21456000)|STATEMENT_EXECUTE|[11]
14:37:19.021 (21465000)|STATEMENT_EXECUTE|[13]
14:37:19.021 (21469000)|STATEMENT_EXECUTE|[14]
14:37:19.021 (21612000)|HEAP_ALLOCATE|[14]|Bytes:10
14:37:19.021 (21689000)|SYSTEM_METHOD_ENTRY|[14]|ApexPages.addMessage(ApexPages.Message)
14:37:19.021 (21697000)|ENTERING_MANAGED_PKG|
14:37:19.021 (21728000)|VF_PAGE_MESSAGE|Fake Error
14:37:19.021 (21738000)|SYSTEM_METHOD_EXIT|[14]|ApexPages.addMessage(ApexPages.Message)
14:37:19.021 (21746000)|SYSTEM_MODE_EXIT|false
14:37:19.021 (21775000)|CODE_UNIT_FINISHED|PageMessages invoke(click)

Its a lot easier just to add an <apex:pagemessages/> component to the page and rerender that as well:

<apex:page controller="PageMessages">
  <apex:pageMessages id="msgs"/>
  <apex:form >
    <apex:pageBlock title="Page Messages Test 1">
      <apex:pageBlockButtons >
         <apex:commandButton value="Click Me" action="{!click}" rerender="counter,msgs"/>
      </apex:pageBlockButtons>
      <apex:outputLabel value="Count  = " />
      <apex:outputText value="{!countVal}" id="counter"/>
    </apex:pageBlock>
  </apex:form>
</apex:page>

which displays the error upon clicking the button:


Another scenario where its pretty much impossible to locate the problem without an  <apex:pageMessages/> component is where there is a required field on the page:

<apex:page controller="PageMessages">
  <apex:pageMessages id="msgs"/>
  <apex:form >
    <apex:pageBlock title="Page Messages Test 1">
      <apex:pageBlockButtons >
         <apex:commandButton value="Click Me" action="{!click}" rerender="counter,msgs"/>
      </apex:pageBlockButtons>
      <apex:outputLabel value="Text1 = " />
      <apex:inputText value="{!text}" required="true"/>
      <br/>
      <apex:outputLabel value="Count = " />
      <apex:outputText value="{!countVal}" id="counter"/>
    </apex:pageBlock>
  </apex:form>
</apex:page>

This time the controller doesn't fake an error, the counter value is simply updated:

public with sharing class PageMessages {
 public Integer countVal {get; set;}
 public String text {get; set;}
 
 public PageMessages()
 {
  countVal=1;
 }
 
 public void click()
 {
  countVal++;
 }
}

As the required component isn't an <apex:inputField/>, there is no decoration of the element to indicate that it is required.  Clicking the 'Click Me' button once again appears to do nothing.


Checking the log this time reveals nothing that can help.  The click method simply isn't being called:

25.0 APEX_CODE,FINEST;APEX_PROFILING,INFO;CALLOUT,INFO;DB,INFO;SYSTEM,DEBUG;VALIDATION,INFO;VISUALFORCE,INFO;WORKFLOW,INFO
15:06:55.036 (36399000)|EXECUTION_STARTED
15:06:55.036 (36462000)|CODE_UNIT_STARTED|[EXTERNAL]|06680000000Tmbw|VF: /apex/kab_tutorial__PageMessage1
15:06:55.046 (46438000)|CODE_UNIT_STARTED|[EXTERNAL]|01p80000000cOqp|PageMessages <init>
15:06:55.046 (46470000)|SYSTEM_MODE_ENTER|true
15:06:55.047 (47466000)|HEAP_ALLOCATE|[EXTERNAL]|Bytes:8
15:06:55.047 (47490000)|HEAP_ALLOCATE|[EXTERNAL]|Bytes:552
15:06:55.047 (47513000)|METHOD_ENTRY|[1]|01p80000000cOqp|PageMessages.PageMessages()
15:06:55.047 (47539000)|STATEMENT_EXECUTE|[1]
15:06:55.047 (47659000)|SYSTEM_MODE_ENTER|false
15:06:55.047 (47678000)|HEAP_ALLOCATE|[EXTERNAL]|Bytes:5
15:06:55.047 (47694000)|STATEMENT_EXECUTE|[1]
15:06:55.047 (47707000)|SYSTEM_MODE_EXIT|false
15:06:55.047 (47720000)|METHOD_EXIT|[1]|PageMessages
15:06:55.047 (47763000)|VARIABLE_SCOPE_BEGIN|[5]|this|KAB_TUTORIAL.PageMessages|true|false
15:06:55.047 (47808000)|HEAP_ALLOCATE|[EXTERNAL]|Bytes:12
15:06:55.047 (47845000)|HEAP_ALLOCATE|[EXTERNAL]|Bytes:12
15:06:55.047 (47889000)|VARIABLE_ASSIGNMENT|[5]|this|{}|0x549e58c6
15:06:55.047 (47910000)|SYSTEM_MODE_ENTER|false
15:06:55.047 (47921000)|HEAP_ALLOCATE|[2]|Bytes:5
15:06:55.047 (47935000)|STATEMENT_EXECUTE|[1]
15:06:55.047 (47944000)|HEAP_ALLOCATE|[2]|Bytes:5
15:06:55.047 (47956000)|STATEMENT_EXECUTE|[2]
15:06:55.047 (47974000)|STATEMENT_EXECUTE|[3]
15:06:55.047 (47991000)|STATEMENT_EXECUTE|[6]
15:06:55.048 (48000000)|STATEMENT_EXECUTE|[7]
15:06:55.048 (48042000)|METHOD_ENTRY|[7]|01p80000000cOqp|KAB_TUTORIAL.PageMessages.__sfdc_countVal(Integer)
15:06:55.048 (48098000)|HEAP_ALLOCATE|[2]|Bytes:12
15:06:55.048 (48122000)|HEAP_ALLOCATE|[2]|Bytes:12
15:06:55.048 (48142000)|VARIABLE_ASSIGNMENT|[-1]|this|{}|0x549e58c6
15:06:55.048 (48155000)|HEAP_ALLOCATE|[2]|Bytes:8
15:06:55.048 (48190000)|VARIABLE_ASSIGNMENT|[-1]|value|1
15:06:55.048 (48205000)|HEAP_ALLOCATE|[2]|Bytes:8
15:06:55.048 (48231000)|VARIABLE_ASSIGNMENT|[2]|this.countVal|1|0x549e58c6
15:06:55.048 (48260000)|METHOD_EXIT|[7]|01p80000000cOqp|KAB_TUTORIAL.PageMessages.__sfdc_countVal(Integer)
15:06:55.048 (48275000)|SYSTEM_MODE_EXIT|false
15:06:55.048 (48292000)|CODE_UNIT_FINISHED|PageMessages <init>
15:06:55.048 (48814000)|VF_SERIALIZE_VIEWSTATE_BEGIN|06680000000Tmbw
15:06:55.050 (50548000)|VF_SERIALIZE_VIEWSTATE_END
15:06:55.440 (54027000)|CUMULATIVE_LIMIT_USAGE
15:06:55.440|LIMIT_USAGE_FOR_NS|KAB_TUTORIAL|
  Number of SOQL queries: 0 out of 100
  Number of query rows: 0 out of 50000
  Number of SOSL queries: 0 out of 20
  Number of DML statements: 0 out of 150
  Number of DML rows: 0 out of 10000
  Number of script statements: 3 out of 200000
  Maximum heap size: 0 out of 6000000
  Number of callouts: 0 out of 10
  Number of Email Invocations: 0 out of 10
  Number of fields describes: 0 out of 100
  Number of record type describes: 0 out of 100
  Number of child relationships describes: 0 out of 100
  Number of picklist describes: 0 out of 100
  Number of future calls: 0 out of 10

15:06:55.440|CUMULATIVE_LIMIT_USAGE_END

15:06:55.054 (54063000)|CODE_UNIT_FINISHED|VF: /apex/kab_tutorial__PageMessage1
15:06:55.054 (54077000)|EXECUTION_FINISHED

In fact what has happened is the client side validation has failed and the page is simply redrawn. However, at this point its very easy to start doubting your code and rewriting it randomly - especially if its more complex than my simple example.  Simply adding an <apex:pageMessages/> component that is rerendered avoids all this:

<apex:page controller="PageMessages">
  <apex:pageMessages id="msgs"/>
  <apex:form >
    <apex:pageBlock title="Page Messages Test 1">
      <apex:pageBlockButtons >
         <apex:commandButton value="Click Me" action="{!click}" rerender="counter,msgs"/>
      </apex:pageBlockButtons>
      <apex:outputLabel value="Text1 = " />
      <apex:inputText value="{!text}" required="true"/>
      <br/>
      <apex:outputLabel value="Count = " />
      <apex:outputText value="{!countVal}" id="counter"/>
    </apex:pageBlock>
  </apex:form>
</apex:page>

Clicking the button this time shows the validation error.


As an aside,  due to the required element not being an <apex:inputField/>, the error message is not particularly helpful.  For details on how to improve this, take a look at one of my older posts.