jump to navigation

Getting ActionSupport to Rerender Elements on a Page June 23, 2010

Posted by mgsmith in : SalesForce.com , add a comment

Up until today I have been struggling with getting the apex:actionSupport tag to work as documented. Apparently, it looks like VisualForce is extremely picky about what it will rerender. I found this great post on the developer boards that resolved my issue.

http://community.salesforce.com/t5/Visualforce-Development/actionSupport-rerender-problem/m-p/150628/highlight/true#M17485

What it came down to was making sure that the elements being rerendered did not have a rendered=”" attribute in the tag. For example, in the code below I wanted to hide the second picklist if the value of the first picklist was ‘Standard’. I kept trying to rerender the pageBlockSectionItem area and it wouldn’t work. By wrapping the pageBlockSectionItem block inside of another tag (pageBlockSection in this case) and then rerendering that ID in the actionSupport tag. I tried wrapping it in an outputPanel tag, but that screws up the formatting of the labels and fields. The same was true for the customConfigurationPanel area. I wrapped that pageBlock in an outputPanel tag and made sure the rendered attribute was on the pageBlock not the outputPanel tag. Works great.

<apex:pageBlock id="MainConfigurationBlock" title="Standard Integration Options"
<apex:pageBlockSection id="configTypeSection" columns="1">
   <apex:pageBlockSectionItem >
      <apex:outputLabel>Integration Type</apex:outputLabel>
      <apex:selectList value="{!config.IntegrationType__c}" size="1">
         <apex:selectOption itemValue="Standard" itemLabel="Standard"/>
         <apex:selectOption itemValue="Custom" itemLabel="Custom"/>
         <apex:actionSupport event="onchange" status="StatusChange"
          rerender="activityObjectSection,customConfigurationPanel" />
      </apex:selectList>
   </apex:pageBlockSectionItem>
</apex:pageBlockSection>

<apex:pageBlockSection id="activityObjectSection" columns="1">
   <apex:pageBlockSectionItem rendered="{!config.IntegrationType__c = 'Custom'}">
      <apex:outputLabel id="activityObjectLabel">Activity Object</apex:outputLabel>
      <apex:selectList id="activityObjectField" value="{!config.ActivityObject__c}" size="1"
      rendered="{!config.IntegrationType__c = 'Custom'}" >
         <apex:selectOptions value="{!objects}"/>
      </apex:selectList>
   </apex:pageBlockSectionItem>
</apex:pageBlockSection>
</apex:pageBlock>

<apex:actionStatus startText="Updating page ..." id="StatusChange"/>

<apex:outputPanel id="customConfigurationPanel">
   <apex:pageBlock id="customConfigurationBlock" title="Custom Integration Options"
   rendered="{!config.IntegrationType__c='Custom' && config.ActivityObject__c != null}">

    ..... other fields that I don't want displayed  ....
    ..... if the IntegrationType is "Standard"      ....

   </apex:pageBlock>
</apex:outputPanel id="customConfigurationPanel">
  • Share/Bookmark

Hiding Edit Links for Related Lists/Views on a VisualForce Page June 16, 2010

Posted by mgsmith in : SalesForce.com , 1 comment so far

I had a situation the other day where I needed to hide the Edit & Del links that appear on both the ListView and Related Lists. Luckily in both cases the pages were VisualForce pages, but I did not want to manually recreate the ListViews or Related Lists by hand.

My solution was to use jQuery to quickly select and hide all elements that had a class of “actionLink”:

<apex:page tabStyle="Workshops__tab">

<apex:includeScript value="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"/>

<apex:sectionHeader title="Data Entry Portal" subTitle="Workshops" />
<apex:pageMessages />

	<apex:ListViews type="Workshop__c" />

<script>
    // Using jQuery, hide all of the actionLinks (edit link specifically) on the Workshops
    // This forces the user to click the Workshop name and then click the [Edit] button
    $(".actionLink").css("display","none");
</script>

</apex:page>

I also posted an Idea to the Idea Exchange to allow easy selection of which links should appear on the standard List Views and Related Lists: https://sites.secure.force.com/ideaexchange/ideaView?c=09a30000000D9xt&id=08730000000I3uN

  • Share/Bookmark

Aggregate SOQL Functions February 3, 2010

Posted by mgsmith in : SalesForce.com , 2comments

I couldn’t be happier that the new 18.0 (Spring ’10) API Release from SalesForce.com now supports aggregate functions in SOQL.

Links to resources:

The new SUM() and GROUP BY features can really simplify writing custom Roll-up Triggers.

Below is a relatively simple Trigger that rolls up Budget numbers by the Previous, Current and Next year to the parent Program__c object. By using the Calendar_Year() function, you can see how easy it is to create a single SOQL statement that does the summing in a single statement.

trigger Program_Budget_Rollup on Program_Budget__c (after delete, after insert, after update) {

    Set<String> programIDs = new Set<String>();

    //************************************************
    // Build a LIST of Program ID's that will
    // need recalculating
    //************************************************
    if(Trigger.isInsert || Trigger.isUpdate){
        for(Program_Budget__c te : trigger.new){
            if(te.Program__c != null){
                if(!programIDs.contains(te.Program__c)) programIDs.add(te.Program__c);
            }
        }
    }  // INSERT/UPDATE Trigger

    if(Trigger.isDelete || Trigger.isUpdate){
        for(Program_Budget__c te : trigger.old){
            if(te.Program__c != null){
                if(!programIDs.contains(te.Program__c)) programIDs.add(te.Program__c);
            }
        }
    }  // DELETE/UPDATE Trigger

    if(programIDs .size() > 0) {

        Map<ID, Program__c> programs = new Map<ID, Program__c>();
        Program__c d = null;

        for (AggregateResult dr : [SELECT Program__c, CALENDAR_YEAR(Date_c) Year, SUM(Amount__c) Amount
        FROM Program_Budget__c GROUP BY Program__c, CALENDAR_YEAR(Date__c)]) {

            String dID = (string)dr.Get('Program__c');
            // get the record or create a new one
            if (programs.get(dID) == null)
                d = new Program__c(ID = dID,
                Previous_Year_Budget__c = 0,
                Current_Year_Budget__c = 0,
                Next_Year_Budget__c = 0,
                Total_Budget__c = 0);
            else
               d = programs.get(dID);

            // update the donation total fields
            Decimal amt = (Decimal)dr.Get('Amount');
            String fyr = (String)dr.GetDate('Year');
            Integer yr = Integer.valueOf(fyr);
            if (yr == Date.today().year()) d.Current_Year_Budget__c = amt;
            if (yr == (Date.today().year() - 1)) d.Previous_Year_Budget__c = amt;
            if (yr == (Date.today().year() + 1)) d.Next_Year_Budget__c = amt;
            d.Total_Budget__c = d.Total_Budget__c + amt;

            // push the record back into the Map
            programs.put(dID, d);
        }

        //commit the changes to Salesforce
        update programs.values();

    }

}
  • Share/Bookmark

Hiding Custom Buttons on a VisualForce Page October 16, 2009

Posted by mgsmith in : SalesForce.com , 7comments

Sorry it’s been so long time since my last blog post. Life and work just seem to get in the way.

I recently wanted to setup some custom buttons that could be dynamically hidden or renamed on the page. As you probably know, SalesForce.com does not currently have the ability to hide buttons on a page layout. However, it can be done through a combination of VisualForce and JavaScript.

Idea to vote on: http://ideas.salesforce.com/article/show/101209/Limit_the_Visibility_of_a_Custom_Button

The most important thing to note here is that this can only be done on a VisualForce page. It’s not possible to hide or take any actions on custom buttons that are on a standard page. This is due to cross-site scripting limitations of all browsers that prevent JavaScript from modifying the DOM of a window at another domain. You’ll see why as we get into the coding.

To start, I’ve taken a simple custom object and created an even simpler VisualForce page to use for the VIEW. Once created, just override the VIEW option with this page.

<apex:page standardController="Application__c" title="Application For {!Application__c.Contact_Name__c}" >
    <apex:Detail subject="{!Application__c.ID}" relatedList="true" />
</apex:page>

At first, the result is visually the same. Now let’s add a custom button to the page.

Screenshot_NewButton

In this case, I named the button “Update_Status”. The ‘Name’ is critical to modifying the button in the VisualForce. Though, the name must always be lowercase in your VisualForce page. For example, even though I have “Update_Status” as the Name here, my VisualForce code will reference “update_status”.

Now comes the fun. By adding some JavaScript to the VisualForce page you can manipulate the button – hide it, disable it, or even change the button label.

<script>
function hideButton(btnName) {
  try{
    var buttons = parent.document.getElementsByName(btnName);
    for (var i=0; i < buttons.length; i++) {
      buttons[i].className="btnDisabled ";
      buttons[i].disabled=true;
      buttons[i].type='hidden';
    }
  } catch(e) {
    // var ee = e.message || 0; alert('Error: \n\n'+e+'\n'+ee);
  }
}

function renameButton(btnName, newTitle) {
  try{
    var buttons = parent.document.getElementsByName(btnName);
    for (var i=0; i < buttons.length; i++) {
      buttons[i].value=newTitle;
    }
  } catch(e) {
    // var ee = e.message || 0; alert('Error: \n\n'+e+'\n'+ee);
  }
}
</script>

We’ll start with the above two functions. By passing in a button name to the hideButton() function we can hide it on the page. Passing in the same button name and a new title to renameButton() will change the button label on the page. Below is my full VisualForce page code:

<apex:page standardController="Application__c" title="Application For {!Application__c.Contact_Name__c}" >
    <apex:Detail subject="{!Application__c.ID}" relatedList="true" />

<script type="text/javascript">
// The code below is executed as soon as the page loads. Based on the value of the Status__c field
// it either hides or renames the update_status button
if ('{!Application__c.Status__c}' == 'Submitted') renameButton("update_status", "Mark as In-Review");
if ('{!Application__c.Status__c}' == 'In-Review') hideButton("update_status");
if ('{!Application__c.Status__c}' == 'Deposit Pending') renameButton("update_status", "Confirm Deposit Received");
if ('{!Application__c.Status__c}' == 'Deposit Received') hideButton("update_status");
if ('{!Application__c.Status__c}' == 'Approved') hideButton("update_status");
if ('{!Application__c.Status__c}' == 'Rejected') hideButton("update_status");

function hideButton(btnName) {
  try{
    var buttons = parent.document.getElementsByName(btnName);
    for (var i=0; i < buttons.length; i++) {
      buttons[i].className="btnDisabled ";
      buttons[i].disabled=true;
      buttons[i].type='hidden';
    }
  } catch(e) {
    // var ee = e.message || 0; alert('Error: \n\n'+e+'\n'+ee);
  }
}

function renameButton(btnName, newTitle) {
  try{
    var buttons = parent.document.getElementsByName(btnName);
    for (var i=0; i < buttons.length; i++) {
      buttons[i].value=newTitle;
    }
  } catch(e) {
    // var ee = e.message || 0; alert('Error: \n\n'+e+'\n'+ee);
  }
}
</script>

Screen captures of the page with button showing and hidden:

ScreenCapture_WithButtonScreenCapture_WithoutButton

  • Share/Bookmark

Change a Tab to Open to a Default View June 5, 2009

Posted by mgsmith in : SalesForce.com , 8comments

view-recent-itemsBy default, when you click on any standard SalesForce.com Tab for an Object, it brings you to a list of “Recently Viewed” items. I, like many others, find this annoying. I’d much rather that SalesForce.com give us options to change the default View by Tab, ideally for each user Profile.

In the mean time, the work-around was to use an S-Control to redirect the user to the custom view. However, with the upcoming deprecation of S-Controls in 2010, we should be writing these as VisualForce pages instead.

S-Control Version:

<html>
<head>
<script language="Javascript">
function init()
{
     parent.document.location.href= "/003?fcf=00B30000005a5FO";
}
</script>
</head>
<body onload="init()">
<center><h2>Please Wait ..... Loading Contacts Tab</h2></center>
</body>
</html>

VisualForce Version

So, I decided to take a stab at converting a more sophisticated version of the above S-Control into a VisualForce page. The key, though, was to completely avoid using Apex so that the page can be modified in Production without having to go through the Sandbox/Test/Deploy steps required with Apex. This is accomplished by using JavaScript for the page logic, basically the same as the S-Control. It just has to be wrapped within the Apex:Page tags and the startup JavaScript logic is slightly different.

The resulting VisualForce page shown below does the following:

<apex:Page tabStyle="Contact" >
<script src="/soap/ajax/15.0/connection.js"></script>
<script type="text/javascript" />
<script>
     window.onload = function() {

     sforce.connection.sessionId = '{!$Api.Session_ID}';

     var describeSObjectResult = sforce.connection.describeSObject("contact");
     var prefix = describeSObjectResult.keyPrefix;

     // Determine the View based on the Role of the User
     var cView;
     switch ( "{!$UserRole.Name}" ) {
     case "North America": cView = "00B30000001Ysw2" ; break ;
     case "EMEA": cView = "00B30000001Ysw4" ; break ;
     case "Europe": cView = "00B30000001Ysw5" ; break ;
     case "SEA": cView = "00B30000001Ysw6" ; break ;
     case "South East Asia": cView = "00B30000001Ysw7" ; break ;
     case "Australia": cView = "00B30000001Ysw8" ; break ;
     default: cView = "00B30000001Ysw3"; break;
     }

     // Change the whole window to point to this location
     parent.document.location.href = "/" + prefix + "?fcf=" + cView ;
}
</script>
<center><h2>Please Wait ..... Loading 'Your' Tab</h2></center>
</apex:page>

To implement this in your Org:

  • Share/Bookmark

Falling Behind May 27, 2009

Posted by mgsmith in : Miscellaneous , 1 comment so far

OK – It’s been a while since I’ve had a blog post and it’s just starting to get on my nerves. As you might expect, it’s a time thing. Just not enough of it – Summer is coming, I’m out biking more, work has started to pick up. All good things, but still gotta find more time to blog again.

In the mean time SalesForce.com is preparing to release Summer ‘09 (API 16) soon. Personally, I can’t wait. Looks like some very exciting new features are coming our way. The 100 page Release Notes document gives you an idea of the scope of the release. Lots of work in the API and the DaaS (Development as a Service) support. There are still some missing pieces in the Metadata API needed for true migration support, but they’re getting closer. Hopefully they’ll add in Metadata types for areas such as Lead Settings, Opportunity Settings, Big Deal Alerts, Assignment Rules, etc.

On the plus side, I just got my Zune back yesterday. It was out for warranty repair for a couple of weeks. Definitely missed it.

Hopefully I’ll have a chance to write up something good on the new Summer ‘09 release soon.

  • Share/Bookmark

Mass Contact Transfer – Part 2 (The Controller) April 20, 2009

Posted by mgsmith in : SalesForce.com , 8comments

Click on the SourceCode tab above to view the full sourcecode for the application or to install a complete working version into your environment.

In the last blog post I went into some of the details behind the Mass Contact Transfer VisualForce page. In this post, I’ll delve into the page Controller and supporting classes that contain the business logic for the page.

The transferContacts class is the controller for the VisualForce page. The controller has four main functions:

Get/Set methods

Generally these are very simple methods to either set the value of a public variable or return that value. Get/Set methods are always preceded by the word get or set followed by the name of the variable. On the VisualForce page these are referenced as {!fieldname}. For example, using value=”!fromUserID}” on a VisualForce page will call the getfromUserID() method to retrieve the value and setfromUserId() to set the value when the form is submitted.

// Variables
public string fromUserID;
public string toUserID;

// Get/Set Methods for the FROM and TO UserID's
public String getfromUserID()   { return fromUserID; }
public String gettoUserID()     { return toUserID; }
public void setfromUserID(string userID)  { this.fromUserID = userID; }
public void settoUserID(string userID)    { this.toUserID = userID; }

The From and To User picklist fields

Rather than require users to use a search lookup for users on the transfer form, I implemented this using two picklists for the From and To users. The FromUsers list includes all Users while the ToUsers lists only includes Active users. The setup behind the two picklists fields is very straight forward.

VisualForce page code for a picklist. The codeblock below accesses the getFromUsers() method to fill the values in the tag.

<apex:selectList value="{!fromUserID}" size="1" required="false"  id="fromUserID">
<apex:selectOptions value="{!FromUsers}"></apex:selectOptions>
</apex:selectList>

Apex Controller code to return the picklist of values. The getFromUsers() method returns a List object of Users sorted by Name. The SelectOption list type accepts two values – an ID and a display name. When a FromUser name is selected from the list, the Apex Controller will be able to access the ID of that user by referencing the toUserID variable. This is set by the page to the ID of the selected when the form is submitted.

public List<SelectOption> getFromUsers() {
    List<SelectOption> options = new List<SelectOption>();
    options.add(new selectOption('', '--- select a User ---'));
    for (User u : [Select ID, Name FROM User Order by Name]) {
            options.add(new selectOption(u.ID, u.Name));
    }
    return options;
}

Searching for Contacts

The doSearch() method is called when the [Find] button is clicked on the page.

<apex:commandButton title="Find" value="Find" action="{!doSearch}"/>

The overall goal of the method is to build an SOQL string, run the Query, and display a list of contacts on the page. As discussed in Part 1, the searchCriteria() class contains the methods necessary to generate the actual WHERE clause parts to build the query string. The logic behind this is shown below.

To start, a base SOQL string is built selecting all the fields in the Contact object along with key fields from the Account, Owner, Account.Owner, CreatedBy.Owner, and LastModifiedBy.Owner relationships. The initial contents of the WHERE exclude Contacts where the current owner is the owner to transfer contacts to (in other words, they own the contacts already).

// Build the base SOQL String, querying the standard Contact fields
// WHERE the current OwnerID = the selected value
string cSOQL = 'SELECT ' + contactFieldsList + ', Account.Name, Account.Site, Account.Owner.Name, '  +
'Account.Industry, Account.Type, Account.Owner.Alias, ' +
'Owner.Name, Owner.Alias, CreatedBy.Name, CreatedBy.Alias, LastModifiedBy.Name, LastModifiedBy.Alias ' +
'FROM Contact WHERE OwnerID <> \'' + toUserID + '\' ';

If a To Owner was selected, add to the WHERE clause to restrict the list of contacts owned by the To User ID.

// If a From User was selected, add this to the criteria
if (fromUserID <> null) cSOQL += ' AND OwnerID = \'' + fromUserID + '\' ';

For each line of Criteria, call the BuildWhereClause() method in the searchCriteria class.

// For each criteria line item, call the method to build the where clause component
for (searchCriteria cl : criteriaLine) {
cSOQL += cl.buildWhereClause(DebugMode);
}

Finally, order the list by Account Name and then Contact Name and limit the list to the first 250 contacts.

// Sort the results and limit to the first 250 rows
cSOQL += ' ORDER BY Account.Name, Name LIMIT 250' ;

searchCriteria class

The BuildWhereClause() to build the WHERE clause component for each of the search criteria lines is where it got complex. This method had to generate code to handle different field types (Text, PickList, Boolean, Number, Date, DateTime, etc.), different operators (=, >, <, contains, IN, etc.) and different types of values. Most of the logic is fairly straight forward, checking the field type and the operator and generating errors where the two are not compatible (BOOLEAN and ‘Less Than’, for example).

The complex part of the logic is to handle Date and DateTime field types. The most recent version of the application now supports the date format of the current user when parsing the value entered. Where this became tedious was for DateTime types. For example, you might expect the following SOQL WHERE block to only retrieve Contacts created on April 1, 2009: WHERE CreatedDate = 2009-04-01. However, CreatedDate is a DateTime field and requires a timestamp, not just a date. In order to properly query Contacts created on 4/1/09 the actual WHERE clause needs to looks like: WHERE (CreatedDate >= 2009-04-01T00:00:00Z AND CreatedDate <= 2009-04-01T23:59:59Z)

Transferring the Contacts

The final step in the process is to transfer the selected contacts to the ‘To User’. The toTransfer() method starts by building a list of Contact ID’s for selected Contacts. Part 1 of this blog post goes into the checkbox on each Contact. Only checked contacts are transferred. The list of ID’s is passed to a Query to select the Contacts. Finally, the OwnerID for each Contact is changed and the Database.Update() method is called to do the actual transfer.

// Build a list of Contact ID's to transfer ownership for
List<string> IDs = New List<string>();
for (transferContactSearchResults c : searchResults) {
if (c.selected) IDs.add(c.contact.ID) ;
}

// Query the contacts being transferred
List<Contact> contacts = [SELECT ID, OwnerID, Name, Account.Name, Title, Owner.Alias FROM Contact WHERE ID IN :IDs];
for (Contact c : contacts) {
c.ownerID = toUserID ;
}

// Process Errors and Count the Number of Records Transferred
Integer transferCount = 0;
List<database.saveresult> srs = database.update(contacts);
for (database.saveresult sr : srs) {
if (!sr.isSuccess()) {
      	ApexPages.AddMessage(new ApexPages.Message(ApexPages.Severity.FATAL, sr.getId() + '/' + sr.getErrors()[0].getMessage() ));
} else {
      	transferCount++;
      }
}

At the very end, the doSearch() method is called to look for additional contacts if there were more than 250 returned in the first query call.

Quick links to Class source code:

The final part of this blog post series will go into building the test class.

  • Share/Bookmark

Mass Contact Transfer – Part 1 (The Page) April 2, 2009

Posted by mgsmith in : SalesForce.com , 10comments

At my previous employer one of my regularly tedious tasks was to mass re-assign contacts based on various conditions. Many times it was as simple as a group of Accounts were transferred to new Account Managers and the default behavior of SalesForce.com is to only transfer contacts owned by the Account owner. The result would be a group of Contacts needed to be transferred manually. I handled these requests using an exported report of Contacts with Contact ID with Ron Hess’s Excel Connector to update the OwnerID column for the contacts. masstransfercriteria Clearly this required a better way – and VisualForce provided the platform. Creating the Mass Contact Transfer VisualForce page was for the most part fairly straight forward. It’s essentially a two part page:

  1. Search Criteria
  2. Search Results

Building the Search Criteria Portion

Retrieving the From and To User ID’s.

To simplify the process of selecting the From and To users, this was constructed using two picklist fields instead of two text fields with a lookup. The “From” picklist includes all Users – active or in-active; the “To” picklist only lists active users.  

<apex:pageBlockSection columns="3" id="UserSelection">
   <apex:pageBlockSectionItem id="FromUser">
       <apex:outputLabel >Transfer From User:</apex:outputLabel>
       <apex:selectList value="{!fromUserID}" size="1" required="false" id="fromUserID">
            <apex:selectOptions value="{!FromUsers}"></apex:selectOptions>
       </apex:selectList>
   </apex:pageBlockSectionItem>
   <apex:pageBlockSectionItem id="ToUser">
       <apex:outputLabel >Transfer To User:</apex:outputLabel>
       <apex:outputPanel layout="block" styleClass="requiredInput">
       <apex:outputPanel layout="block" styleClass="requiredBlock"/>
       <apex:selectList value="{!toUserID}" size="1" required="false"  id="toUserID">
           <apex:selectOptions value="{!ToUsers}"></apex:selectOptions>
       </apex:selectList>
       </apex:outputPanel>
   </apex:pageBlockSectionItem>
</apex:pageBlockSection>

Mass Contact Transfer - Required FieldThe tough part was getting the red required bar next to the “To” user field. You would expect that setting the Required attribute on the apex:selectList tag would tell SalesForce.com to use the standard red bar for the field, but that is not the case. Instead you have to wrap the field with an apex:outputPanel tag and have another apex:outputPanel tag within the first that closes itself. This code is highlighed above in red. The syntax should work for any field that you want to show the SalesForce.com standard red bar to indicate a required field.

Search Criteria Section

Next was to build the Selection Criteria section of the page. Initially it was very simple. I wanted five lines for selection criteria to match the standard Transfer pages. The first draft of the page had five repeated blocks of code in the VisualForce page for the Selection Criteria (field, operator, value). Clearly this was not the most efficient way to write the page, but it gave me a feel for the look and what was needed. The more efficient way is to use a separate class for Selection Criteria. The Page Controller constructor method creates a List with 5 instances of the searchCriteria class and the VisualForce page can use an apex:DataTable tag to display however many rows are in that list. The resulting page looks like this:

<apex:dataTable value="{!searchCriteria}" columns="3" var="criteria">
   <apex:column>
      <apex:selectList value="{!criteria.searchField}" size="1" id="SearchField" >
        <apex:selectOptions value="{!searchFields}"></apex:selectOptions>
      </apex:selectList>
   </apex:column>
   <apex:column>
      <apex:selectList size="1" value="{!criteria.searchOperator}" id="SearchOperator1">
        <apex:selectOptions value="{!criteria.OperatorSelectList}"></apex:selectOptions>
      </apex:selectList>
   </apex:column>
   <apex:column>
     <apex:inputText size="20" id="SearchValue1" value="{!criteria.searchValue}"/>
   </apex:column>
</apex:dataTable>

The searchCriteria class itself provides the standard get/set methods for the three columns:

selectioncriteria When the user clicks the [Find] button on the page, the Page Controller Class is able to loop through the criteria lines and process each one individually. Since the Search Criteria section is written to be independent of the “Mass Contact Transfer” page, there is a method in the searchCriteria class to build the Where clause portion. The doSearch() method in the Page Controller Class appends each of the Where clause parts together to create a single SOQL statement to query the contacts based on the From UserID, To UserID, and user defined criteria. The Query is run and the results are appended to a searchResults List for display.

Search Results

Mass Contact Transfer Search Results

At the bottom of the page there is a section that starts as hidden using the style attribute on the apex:outputPanel tag. {!ShowBlockIfResults} returns either “display: block;” to show the block (only if there are search results) or “display: none;” to hide the block. In the list of Contacts, a checkbox in the first column is used to allow the user to select which contacts should be transferred. This is checked by default. To simulate how the standard SalesForce Transfer pages work, the user can check or uncheck all Contacts by clicking the checkbox in the column header. This is accomplished using some simple JavaScript code linked to onClick event on the checkbox in the column header. The styleClass, rowClasses, onrowmouseout, and onrowmouseover attributes of the apex:DataTable tag are used to format the results table so it looks like the standard SalesForce Transfer pages.

<apex:outputPanel id="Results" layout="block" style="{!ShowBlockIfResults}">
<apex:form id="resultsForm" >
  <apex:pageBlock id="resultsBlock">
  <apex:pageBlockButtons >
      <apex:commandButton title="Transfer Selected" value="Transfer Selected" action="{!doTransfer}"/>
  </apex:pageBlockButtons>
      <apex:dataTable value="{!searchResults}" var="Results" id="resultsDataTable"
      styleClass="tableClass list" rowClasses="odd,even"
      onrowmouseout="if (window.hiOff){hiOff(this);}" onrowmouseover="if (window.hiOn){hiOn(this);}">
        <apex:column >
          <apex:facet name="header"><apex:inputCheckbox id="selectall" selected="true"
              onclick="javascript:customSelectAllOrNoneByCheckbox(document.forms['MassTransferContactsPage:resultsForm'],'MassTransferContactsPage:resultsForm:resultsBlock:resultsDataTable:', this);"/></apex:facet>
          <apex:inputCheckbox value="{!Results.selected}" id="selected" />
        </apex:column>
		…. DataTable Columns go here ….
      </apex:dataTable>
   </apex:pageBlock>
</apex:form>
</apex:outputPanel>

Finally, when the user clicks the [Transfer Selected] button, the doTransfer() method on the Page Controller loops through the searchResults list to build a list of Contacts whose OwnerID should be changed. Any errors in the Database.Update call are displayed in the messages section at the top of the page. The last step is to re-run the doSearch() method to query any remaining contacts not transferred the first time (if they were not checked, there was an error, or the original query returned more than 250 rows).

Next week, Part 2 of this blog post will go into the Apex code in the Page Controller and the two supporting classes. In the mean time you are welcome to download the source code or install the AppExchange package by clicking on the SourceCode tab at the top of the page.

  • Share/Bookmark

Poll: Favorite SalesForce.com Browser March 20, 2009

Posted by mgsmith in : Poll , 1 comment so far

In honor of the release of Microsoft’s Internet Explorer 8 today, here’s a quick poll:


  • Share/Bookmark

Volunteer Computing and The Cloud March 20, 2009

Posted by mgsmith in : Miscellaneous , 2comments

Cloud=Freedom I just listened to a recent Floss Weekly Podcast about BOINC, an open source middleware system from Berkeley for Volunteer Computing. You’ve probably heard of Seti@Home or Folding@Home as well as some of the many other collaborative projects out there. This got me thinking about how Volunteer Computing and Cloud Computing relate to one another.

Certainly, these are not technically the same. Volunteer Computing as it’s called refers to distributed processing power over many disparate systems globally using the willingness of others to donate their computers CPU cycles to a larger project. I suppose you could also call it Collaborative Computing. The Cloud, on the other hand, euphemistically refers to the Internet; more specifically to the concept of running an application somewhere out there in the Internet where you as the user are utilizing an industry standard Web-browser to access the application. The application in the cloud may be hosted on a single server or more likely, as with SalesForce.com, Google Applications, Amazon S3 and others, the application is distributed over multiple data centers providing redundancy and improved performance.

In my opinion, though, where the two meet is in the idea of distributing computing. Both technologies utilize the power of multiple systems, geographically distributed, and yet connected through the power of the Internet. In Volunteer computing there is a single goal – to collaboratively help out an important scientific project by donating your computers processing capability. I’ll call this many-to-one. In Cloud Computing there is a single goal too – to build a distributed application to provide a service to their clients. I’ll call this one-to-many.

If my father were here he’d say that Cloud and Volunteer Computing are the same things he ran on an old IBM System 36 back in the day; with 4kb of RAM and tape drives, up hill, in the snow. And, he’s somewhat right. The computer world has gone from single huge systems providing CPU processing power to multiple users, to millions of standalone systems, to globally networked systems providing collaborative CPU processing to multiple users or towards a single cause.

I suspect we’ll see more and more of the new Netbooks that are becoming popular. These are designed to use Cloud Computing. No Microsoft Office, just Google Documents and GMail.

The big question: As the Cloud grows and our local machines run less local software what happens when the Internet is down? Google Gears are Adobe AIR are a step towards dealing with this potential problem.

On my friend’s blog at Pay4Rides.com, Mat is building a case to harness the power of existing traffic flow to reduce carbon output and reliance on foreign fuels. To me, this sounds a bit similar to Volunteer Computing . In computing, it’s harnessing the existing computers and Internet to power a project. For Pay4Rides, it’s using the existing traffic (as in “I’m driving to work already”) to give rides to others going the same way or the same place. Now, whether Volunteer Computing actually uses more energy because CPU’s that were otherwise idle are now pounding away on complex calculations is a totally different discussion.

Let me know what you think.

  • Share/Bookmark