Category Archives: jQuery

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>
    

Visualforce – jQuery pattern – enforce only single checkbox in a list

Over the last few months, I’ve dabbled in adding some client-side improvements to Visualforce pages. But, I’ve resisted doing much with Javascript and jQuery in order to keep the development models limited to just Visualforce, Apex, and Force.com. There was something about a blend of Javascript and VF markup on a page that I always found jarring and hard to read and understand – too much syntactic dissonance

But, jQuery and jQuery Mobile are highly recommended for developing Salesforce1 apps. After reading jQuery Mobile Web Development Essentials, Second Edition by Camden and Matthews, I discovered a nice organizing pattern for jQuery that was a good place to add in all of a page’s jQuery event handlers (see chapter 8). So, I decided to apply it to a commonly-occurring problem – display a list with checkboxes next to each row, but allow only one box to be checked before submission to the server. This avoids writing controller code to enforce a single selection (as radiobuttons can’t easily be applied to table rows).

So, here it is where the table displays a list of Quotes on an Opportunity and only one can be specified for the action

<apex:page standardController="Opportunity" extensions="OppoControllerExtension">

	<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
		$j(function() {				// 	jQuery shorthand for $(document).ready(function() ..  Wait until page loads 
			//	define the application
			var	OppoToSvcContract	= {};
			
			(function(app) {		//	Define anon function
				//	--------------------------
				//	variable definitions
				//	--------------------------
		
				//	--------------------------
				//	init:	do initializations
				//	--------------------------
				app.init	= function() {
					app.bindings();		//	establish bindings	
				};
		
				//	-----------------------------------
				//	bindings:  events to event handlers
				//	-----------------------------------
				app.bindings = function() {
					// set up binding for onclick checkbox: Unchecks other checkboxes
					$j('input[id*="qCheckbox"]').click(function(e) {		// attach anonymous fn as event handler to click event for all DOM checkbox whose id attr contains qCheckbox, $j(this) available
						var clickedElmId	= $j(this).attr('id');			// DOM elm that was clicked
						$j('input[id*="qCheckbox"]').each(function() {		// Go thru all checkboxes
							if ($j(this).attr('id') != clickedElmId) {		// if id diff than clicked elm id, uncheck	
								$j(this).removeProp('checked');
							}
						});
					});													// end binding
				};														// end all bindings
				
				app.init();				// Anon function invokes its own init function
			}) (OppoToSvcContract);		// Execute anon function passing in an object representing our application
				
				
		});	
	</script>





	<apex:sectionHeader title="Create a Service Contract from this Opportunity's Quotes:" subTitle="{!Opportunity.name}"/>
	
	<apex:form id="theForm">
		<apex:pageMessages/> 
		<apex:pageBlock >
			<apex:pageBlockButtons location="top">
		 		<apex:commandbutton value="Create Service Contract from Oppo+Quote" 
						action="{!createSvcContract}"/>
		 		<apex:commandbutton value="Cancel" immediate="true" action="{!cancel}"/>      
			</apex:pageBlockButtons>
			
			
		</apex:pageBlock>
		
		<apex:pageBlock title="Quotes (to use as source for Service Contract line items)">
	
			<apex:pageBlockTable id="quotesTbl" value="{!availQuotes}" var="aq" style="width:90%; border-color: rgb(248,248,248);" headerClass="quoteTblHdr">
                <apex:column headerValue="Select One" style="width: 5%;">
                	<apex:inputCheckbox id="qCheckbox" value="{!aq.isSelected}"/>  <!--  jQuery event handler takes care of only one box checkable -->
                </apex:column>   
                <apex:column headerValue="Name" style="width: 10%">
					<apex:outputLink value="{!URLFOR($Action.Quote__c.View,aq.qw.q.id)}">{!aq.qw.q.name} {!aq.forecastMsg}</apex:outputLink> 
				</apex:column>    
			</apex:pageBlockTable>
         </apex:pageBlock>		
	</apex:form>
</apex:page>