Thursday, December 29, 2011

Last Modified Date Rounding to Whole Seconds

The DateTime class in Salesforce has precision down to the order of milliseconds, as implied by the getTime method which "returns the number of milliseconds since January 1, 1970, 00:00:00 GMT represented by this DateTime object." Since the Last Modified Date field is a Date/Time field, one would assume that the Last Modified Date includes the millisecond value when stamping a record, right? Alas, this is not the case.

A video demonstrating the behavior: http://www.youtube.com/watch?v=IyRd1woiS24

It seems that Salesforce only includes the year, month, day, hour, minute and second when stamping the Last Modified Date. The millisecond value is simply zeroed out without any rounding. So, if a record was actually modified at 12/29/2011 12:31:20.578, Salesforce will stamp the record as being modified at 12/29/2011 12:31:20.000.

Okay, so there's a tiny discrepancy. I mean, we're talking tiny differences here. The million-dollar question: Why do I care?

I care because this little quirk caused my unit test to fail over 80% of the time due to the fact that comparing the Last Modified Date to DateTime.now() was producing unexpected results. For example:
  • At the beginning of the test method, DateTime.now() returned 12/29/2011 4:00:01.500.
  • At the end of the test method, the updated record's Last Modified Date shows 12/29/2011 4:00:01.000.
  • System.assert(record.LastModifiedDate >= testStartDateTime) fails unexpectedly and inconsistently.

The oddity with Last Modified Date is not an impossible problem to work around, but should I really be working around it? What does this mean for test-driven development that Apex is supposed to enable?

Thursday, December 22, 2011

Org ID Automatically Replaced in Sandboxes

I discovered something that made my blood run cold today: The OrganizationInfo.isProduction method I was relying on in Apex to communicate to the correct web service endpoint was returning true in my sandbox orgs.

My OrganizationInfo class is super simple, created as suggested in a comment on the IdeaExchange (Determine sandbox name vs production from apex). Shown below for reference:

public class OrganizationInfo {
    
    /**
     * The Organization ID of the production org.
     */    
    private static final Id PRODUCTION_ID =
            '00DA0000000Kb9R';
    
    /**
     * Determine whether the current org is a
     * production org or a sandbox org,
     * based on whether the Org ID matches the
     * production App org's Org ID.
     *
     * @return Whether the org is Production
     */
    public static Boolean isProduction() {
        return UserInfo.getOrganizationId()
                == PRODUCTION_ID;
    }   // public static Boolean isProduction()
    
}   // public class OrganizationInfo

The obvious question: How does something so simple fail in a sandbox org?

The surprising answer: When a sandbox is created or refreshed, Salesforce automatically does a search and replace and replaces the production org ID with the sandbox org ID. Once a sandbox is created or refreshed, a single line of code changes in the above class:

    private static final Id PRODUCTION_ID =
            '00DZ000000056dv';

This tiny, almost unnoticeable change has been screwing everything up for a long time, and its discovery also explains why we would periodically get strange data in production and also strange responses in our sandboxes.

I wish I had known about this earlier, but who would've guessed? Anyhow, the fix that I've implemented (and confirmed by creating a new sandbox) is to split the ID into 3 parts when assigning it to the constant.

    private static final Id PRODUCTION_ID =
            '00DA0' + '00000' + '0Kb9R';

Amazing... the things one discovers in the worst possible ways...

Discrepancies in Reports and Report Actions

I learned an interesting thing about reports today: What's displayed in a report can be different from what gets exported or from what gets added to a campaign. Let me try giving an example to clarify my statement.

Here's what we expect to happen:
  1. Run a Leads report.
  2. Review the data on the report. Let's say that the report shows 3 Leads.
  3. Click Add to Campaign. The 3 Leads we saw are added to a campaign.

Here's what could actually happen:
  1. Run a Leads report.
  2. Review the data on the report. Let's say that the report shows 3 Leads.
  3. Click Add to Campaign. Four (4) Leads, not 3 Leads as we just saw, are added to a campaign.
  4. Return to the report. 4 Leads are now shown, instead of 3.
  5. Click Export and complete the export. Five (5) Leads, not 4 Leads, are exported

The cause of this behavior is that when an action is performed on a report, namely Add to Campaign or Export, the report is run again in the background to as part of performing the action. In other words, what we see on the report before we export or add to a campaign can be thought of as a "preview" of what would happen after we perform an action.

In most cases this is probably a non-issue, but I thought the phenomenon was worth noting in the off case that someone is perplexed by an odd discrepancy between a report and a campaign member list or an export file.

Sunday, December 18, 2011

ApplyYourself Hack to Mass Update Choice Group Values

How annoying is it that there is no easy way to mass update choice group values in ApplyYourself? All mass updates have to be sent to an account manager, who then uses some magical tool to make the changes that should've taken 1 minute for an administrator to complete.

For a change that I needed to make immediately, outside of business hours, I had to come up with an alternative: ChoiceGroupUpdateHack.js

When this script (making sure the code is prefixed with javascript:) is entered into the address bar and executed, a small textarea element is created at the top of the page along with an Update Values button.


To use the hack UI, all one has to do is paste new values directly from Excel into the textarea element and then click the button!


The Excel spreadsheet should be formatted as it is exported from ApplyYourself. The spreadsheet should include the following columns, in order:
  • Choice Value
  • Choice Code
  • Choice Order
  • Header: "Yes" or blank
  • Related Value
  • Inactive Date: MM/DD/YYYY

Although this hack took 3 hours to develop, the ability to mass update choice groups on demand autonomously is priceless to me.

Note: If ApplyYourself starts throwing bizarre errors that don't make sense, another hack may be necessary to clear the choice group before loading the new values: ChoiceGroupClearHack.js

This hack was validated in Safari 5.1.2 and in Google Chrome 16.0 on Mac OS X Lion.

Friday, December 16, 2011

ApplyYourself Hack to Use Free-form Text Filters for Choice Group Fields

I discovered an interesting bug in ApplyYourself that makes it possible to do something that should've been standard functionality: Setup normal text filters using the Contains operator with Choice Group fields.

Imagine trying to get a list of all records that have Program value containing the word "Bachelor" when your Program field is setup as a Choice Group with over 100 options. The standard query interface forces you to use the following filter:
  • Program In this List ... (manually selecting every singe value using the tiny 3-line picklist)

Or, you may have smartly added "Bachelor" as an extra value to the associated Choice Group so that you can select that single value when using the Contains operator.

However, both of these methods are annoyances. What if I wanted to query something on the fly with a value that I haven't predicted to need before?

The hack workaround or solution is simpler than both alternatives:
  1. Setup the filter with the desired field and the Contains operator with any value at all.
  2. Click save and run.
  3. Click the Back button in your browser, not in ApplyYourself. The picklist will have now magically turned into a free-form text field!

Monday, December 12, 2011

Problem with Surveys Asking for "Agree" or "Disagree" Using Radio Buttons

I filled out a short survey today (which I appreciated for being short) that asked me to assess my satisfaction for an event I recently attended. What's notable about the experience was that just before I was about to click the Submit button I decided to check my 3 responses one more time. I was very glad I checked, because I realized that all of my responses were the exact opposite of what I intended, and so I corrected each response before making my final submission.

The problem: I had selected "disagree" for every item with which I agreed and vice versa for ones with which I disagreed.

How often do people encounter surveys that list a bunch of statements and then give radio buttons for indicating one of the following (or similar) sentiments:
  • Strongly agree
  • Agree
  • Neutral
  • Disagree
  • Strongly disagree

I think that based on each person's individual experiences, the person may assume that "Strongly Agree" either always falls on the left or always falls on the right side of the response matrix without pausing to actually read the survey. If the respondent is in a hurry to complete a survey, are the differences between the following two screenshots really all that apparent?



To reduce the chance of survey results being invalidated by responses that are completely wrong because the person intended the response on the other end of the spectrum, picklists may be used in place of radio buttons. I'll outline a few reasons why I think picklists are the better choice.

1. Picklists force people to read.

When a user is confronted with a picklist that starts with an option like "--Select a Response--", he or she must read all of the picklist options in order to pick the right value.

2. Keyboard shortcuts make picklists more usable.

Imagine a standardized picklist that has the following options:
  • Agree
  • Agree, Strongly
  • Disagree
  • Disagree, Strongly
  • Neutral

When a user tabs to the input and when the input has focus, the user can:
  • Press A once for "Agree";
  • Press A twice for "Agree, Strongly";
  • Press D once for "Disagree";
  • Press D twice for "Disagree, Strongly"; or
  • Press N for "Neutral".

In my mind, this makes surveys easier to fill out in less time, which should increase the response rate by telling users that a survey would only take 1 minute instead of 2.

Friday, December 2, 2011

Streamlined Login for MediaWiki

I love MediaWiki overall, but I cannot stand the way the login prompt works when an unauthenticated user tries to access a protected page.

The tedious process is basically as follows:
  1. User clicks a link to a protected page. A page with "duh" instructions is displayed, forcing the user to click a link to get to a login form.
  2. User clicks the link to get to the login form. Why doesn't the login form automatically give focus to the Username field?
  3. User clicks in the Username field just to start typing in a username.

The process really should be as follows:
  1. User clicks a link to a protected page.
  2. User immediately starts typing a username.

So, to make it easier for users to like and adopt our MediaWiki instance, I customized two files: /includes/OutputPage.php and /includes/templates/Userlogin.php.

In case anyone wants to easily copy the code, I've uploaded my notes on this hack. Now I'm finally ready to start publicizing our MediaWiki internally and getting people excited about it!

This hack was tested on the following browsers in Mac OS X Lion:
  • Safari 5.1.1
  • Firefox 8.0.1
  • Chrome 15.0

Note: This implementation causes the instructions page to fully load before JavaScript redirects the user to the actual login page. Any suggestions on how to skip the loading of the instructions page altogether will be much appreciated.

Note: grep -R alone is a significant reason why Linux- and Unix-based OS's (e.g., Mac) are so much better for developers than Windows, out of the box.

Monday, November 28, 2011

Chatter Overload: Chatting in Multiple Orgs

How do people deal with collaborating in multiple Chatter clouds with different groups of people

I'm not sure there's a best answer for this, but for what it's worth here's how I deal with membership in three Chatter clouds.

My three Chatter clouds:
  • Enterprise org: for internal use only
  • App org: also for internal use
  • Higher Ed Cloud: created by the Salesforce Foundation for inter-institutional collaboration

To start my day, I login to both the Enterprise org (hosted on NA1) and the App org (hosted on NA11) with my default browser. This allows me to click links in the Chatter digest and notification emails to go directly to the Chatter thread to post a response. For the Higher Ed Cloud, I'm connected using Chatter Desktop, which allows me to post quick responses to new posts and comments because the composite Chatter feed is sorted by both Post & Comment Date.

I suspect that for those of us who aren't as fortunate to work in orgs that are hosted on different servers, we have to rely on the Chatter Desktop feature that allows fast switching between different orgs, which works pretty well in my opinion.

But in the end, how much collaboration one can handle across all media depends on what each of us value. Setting up Chatter to send notifications on every post and comment anywhere may not be a commitment suitable or desirable for everyone.

Friday, November 25, 2011

Using IsPersonAccount Field in Account Triggers

Great news! It looks like the IsPersonAccount field is always available for use in Apex triggers, without the need to retrieve the field values using SOQL!

The following test was used to reach this conclusion (in Winter '12):
  1. Create a trigger that activates or fires before/after insert, before/after update and before/after delete.
  2. Add code to the trigger to check every record in Trigger.new and in Trigger.old with the following assertion: System.assert(accountInTrigger.get('ispersonaccount') != null);
  3. Create a test method to insert, update and then delete an Account.
  4. Verify that the test passes.

Optionally, one can also use the following code in the trigger to take a deeper look at the basic information that's always available, depending on what caused the trigger to fire:
System.debug('Trigger.new = ' + Trigger.new);
System.debug('Trigger.old = ' + Trigger.old);

Tuesday, November 22, 2011

Link to FAQ in SGHE Customer Support Center

To address a FAQ issue similar to the absence of direct RPE links in the SunGard HE Customer Support Center described in a previous post, hopefully the utility below will give users a way to share FAQ's more quickly and effectively.

FAQ #:
URL:

Thursday, November 17, 2011

Notification of New Comment on Idea in Salesforce

A quick Google search for "salesforce idea new comment notification" revealed that I wasn't the only person frustrated with how un-social Ideas are in the Social Enterprise. My only consolation is that at least it is possible to setup an Email Alert to notify the Idea Creator that a new comment was added, only when a new comment is added and not when the Idea is modified.

To do this, we just need three things:
  • Workflow Rule: Alert Creator on New Comment
  • Email Alert: Idea: New Comment Added
  • Email Template: Idea: New Comment

Workflow Rule: Alert Creator on New Comment

Object: Idea
Evaluation Criteria: Every time a record is created or edited
Rule Criteria: LastCommentDate > LastModifiedDate

Email Alert: Idea: New Comment Added

Object: Idea
Recipients: Record Creator

Email Template: Idea: New Comment

Subject:
New Comment ({!Idea.NumComments}) on Idea: {!Idea.Title}
Plain Text Body:
A new comment has been added to your Idea: https://na11.salesforce.com/ideas/viewIdea.apexp?id={!Idea.Id}

Please use Chatter or email to notify others who may also wish to respond to the comment, since you are the only person who received this notification.

Chatter: https://na11.salesforce.com/_ui/core/chatter/ui/ChatterPage

Thank you

Thank you, everyone who talked about this problem and frustration in the online communities:

Revolutionizing Unit Tests in Salesforce with Public Test Classes

One of the coolest features in Winter '12 for any organization that uses Apex has to be public test classes.

Why? One main reason: Public test classes combined with a tool like HardBoil will revolutionize unit testing in Salesforce!

Imagine a world where functional users (yes, functional users!), not developers, actually setup all of the unit tests through an interactive process that...
  • Saves developers countless hours of writing boilerplate code.
  • Drastically reduces human error in staging test data, checking pre-conditions and asserting post-conditions.
  • Produces clean, self-documenting and easy-to-read test code.
  • And trains functional users to learn the data structures and respect data quality, all at the same time!

Here's the immediately achievable concept: Create a single, public test class (perhaps using HardBoil) called something like EnterpriseTestDb. Then, all one has to do is start every test method by instantiating EnterpriseTestDb as follows:
// Stage test data.

EnterpriseTestDb db = new EnterpriseTestDb();

// Set test parameters.

Account largeAccount =
        db.getAccountByName('Acme Corporation');

Contact mainContact =
        db.getContactByName('John Doe');

...

The best parts about all of this, again, is that...
  • Not a single second of developer time was needed to create potentially hundreds of test records that can support every single unit test in the entire org!
  • Functional users learned the data structure by setting up all of the test data for their developers!

And the long-term concept? Automatically generating test methods already setup to check pre-conditions and assert post-conditions, thereby allowing developers to simply fill in the code in each test method to move from the pre-condition to the post-condition.

In short, I can't wait for this new world to arrive.

Friday, November 11, 2011

Link to RPE in SGHE Customer Support Center

Getting to RPE's in the SunGard HE Customer Support Center seems to be a bit of a chore, and there isn't an intuitive way to grab a URL that can be shared with colleagues or the SGHE community. Hopefully this post will give users a way to share URL's conveniently and get to the content faster.

RPE #:
URL:

Monday, November 7, 2011

assertEquals Method for Multi-Select Picklist Values or Other Delimited Values

I couldn't find a way to easily assert that two multi-select picklist values are identical, especially considering that 'value1;value2' really should be considered equal to 'value2;value1'.

To fix this, I wrote an auxiliary class System2 that has a custom assertEquals method that asserts two Strings contain the same delimited values.

The code is verified by checking the following cases in an associated test class:

s1s2result
pass
afail
afail
aapass
aa;bfail
a;bafail
a;ba;bpass
b;aa;bpass
a;bb;apass
a;b;cc;a;bpass
b;a;ca;b;cpass
a;b;ca;b;c;dfail
a;b;c;da;b;cfail

Saturday, November 5, 2011

JavaScript Hack to Render Red Bar for Required apex:selectList

I'm surprised that Salesforce doesn't support rendering apex:pageBlockSectionItem elements with the same red bar that identifies a required element.

All I wanted was for ...


... to look like ...


So, for consistency of developing a Visualforce page that has a consistent look-and-feel, I came up with a script snippet that seems to work to add the red bar to any apex:pageBlockSectionItem with the dataStyleClass set to "requiredData".

Here's the code:
/**
 * Add the Salesforce "required" appearance to an 
 * input cell.
 *
 * @param td The input cell to give a "required" 
 *           appearance.
 */
function addRequiredAppearance(td) {
    
    // Construct the wrapper div that goes inside 
    // the td.
    
    var requiredInputDiv = 
            document.createElement("div");

    requiredInputDiv.setAttribute(
            "class", "requiredInput");
    
    // Construct and append the div that renders 
    // as the red bar.
    
    var requiredBlockDiv = 
            document.createElement("div");

    requiredBlockDiv.setAttribute(
            "class", "requiredBlock");

    requiredInputDiv.appendChild(
            requiredBlockDiv);
    
    // Move each child from the td inside the 
    // wrapper div.
    
    while (td.firstChild) {
        requiredInputDiv.appendChild(
                td.removeChild(td.firstChild));
    }   // while (td.firstChild)
    
    // Append the wrapper div inside the td.
    
    td.appendChild(requiredInputDiv);
}   // function addRequiredAppearance(td)

// Iterate through every marked element to add 
// the required appearance.

jQuery(".requiredData").each(function (i) {
    addRequiredAppearance(this);
});

With the desired apex:pageBlockSectionItem elements marked, this code adds the red bar to compliment a Visualforce input element that has the required attribute set to "true".

As mentioned in the discussion thread, Salesforce may at any time change the way they render required fields. Since this was only tested in Winter '12, the hack may not work in a later release if applied as-is. However, the concept should still hold true, and as long as the function is modified appropriately all should be well.

Note: This hack uses the jQuery library for selecting marked elements.

Monday, October 24, 2011

Project HardBoil: Day 4.5

Succeess! Without registering the app with Google, HardBoil now works with any Google Account using the AuthSub library for Google Data.

Basically, a user can login, pick a spreadsheet and boil the spreadsheet into code right on the spot. With the right amount of documentation and UI cleanup, I think this could be available for beta testing soon...

Sunday, October 23, 2011

Project HardBoil: Day 4

What a milestone! 842 lines of Apex code generated to create test records for 8 different objects that have relationships between each other.

The back-end code definitely should be cleaned up and re-thought a little bit, but the concept is proved and now the only thing I need to make this available to the world for "beta testing" is to setup the Google authentication scheme.

I really wish that Google would simply make Spreadsheets accessible without having to login. Wouldn't that be so much easier?

Project HardBoil: Day 3

I've finally gotten my PHP code to start boiling Apex. Simply enough, the code it generated right now just looks like:

private class TestRecordSet {
    private Map<Id, Compensation_Rate__c> compensationRateMap;
    private Map<Id, Account> accountMap;
    private Map<Id, License_Certification__c> licenseCertificationMap;
    private Map<Id, Term__c> termMap;
    private Map<Id, Part_of_Term__c> partofTermMap;
    private Map<Id, Part_of_Term_Paydate__c> partofTermPaydateMap;
    private Map<Id, Course__c> courseMap;
    private Map<Id, Class__c> classMap;
}   // private class TestRecordSet

Every related SObject class used by HardBoiler will need to have methods that generate the appropriate code for the appropriate location. For example: SObject instances have a boilApexVariableCode method, a boilApexConstructorCode method and a boilApexGetterCode method.

Friday, October 21, 2011

Project HardBoil: Day 2

As I began writing my code today, I realized that there was a problem with the way I designed the related objects.

My improved scheme includes the following:
  • SObject
  • SObjectField
  • SObjectRecord
  • SObjectRecordFieldValue

I also took the easy way out with getting the spreadsheet to be read in the way I expected by duplicating the field name row and adding two more "column header rows" to represent the SOAPType and whether the field is a custom external ID or not.

Project HardBoil: Day 1

To make HardBoil work really, really well, in the ideal world, it would link to the Google spreadsheet for the data and then validate the spreadsheet and its contents against the developer's Salesforce org.

However, in the real world (and in the interest of actually using this code to meet an immediate need for my own, full-time employer), the ideal will take way too long with too much technical expertise that I just don't have at this time. Also, I don't want to waste time just yet with trying to figure out how to create a GUI that allows the user to login to Google and pick out the spreadsheet with the test data.

So, where does that leave me with a day of progress?

I've created code that begins to define the following classes:
  • HardBoiler
  • SObject
  • SObjectField
  • SObjectRecord

Forcing my way into my Google Account and hard-coding the authentication and spreadsheet selection, I was able to setup HardBoiler to be constructed with a parameter for the spreadsheet's Zend_Gdata_Spreadsheets_WorksheetFeed. This in theory allows HardBoiler to initialize with the right Salesforce records by iterating through each row in each worksheet.

Everything looked good until I got down to the field level for a specific record, where Google did something interesting that screwed up my original plans: Google treats my column header as the key in an array that represents a row of cells in the spreadsheet; but the key is the column-header in lower case with non-alphanumeric characters stripped out.

My original plan was to use the column header to hold the fields' API names, but now it looks like my immediate challenge will be to figure out a plan B for enabling the code to match cell values to fields.

Project HardBoil: Kickoff

So, I've decided to start a project called HardBoil, for lack of a wittier name. What's the goal of this project? To produce a web app that generates boilerplate code for Apex test classes. Specifically, it generates boilerplate code for Apex test classes to create test data to be used in individual unit tests.

What might that code look like? Take a look at the inner TestRecordSet class in this example test class. Some basic elements of the boilerplate code include...
  • List of test records.
  • Map of test records by ID.
  • Map of test records by record name.
  • Constructor that creates the test records and populates the maps.
  • Standard getter methods to get test records by index, ID or record name.

Concept

  1. Create a Google spreadsheet containing test records for different objects, with each object on a separate worksheet.
  2. Login to HardBoiled, point to the spreadsheet, and click a button to generate hundreds of reliable lines of code to setup records for staging best-in-class unit tests in Apex.

Why?

Well, after a few scrambles to fix broken Apex code in a production org, I realized that there is a lot of wisdom in the "Testing Best Practices" article in the Force.com Apex Code Developer's Guide, Version 23.0.

The trouble with implementing this wisdom consistently and repeatedly is that when you have to setup test records for a bazillion objects, the exercise becomes extremely tedious and painful... not to mention error-prone.

Imagine writing the same boilerplate code for 5 objects, with 3 records per object, with each record having 10 fields filled in. At a minimum, you're looking at writing 5 x 3 x 10 = 150 assignment statements. And then, what do you do about Lookup and Master-Detail relationships?

And you have to write this code all over again for every single test class you write? And how do users trust that you really understand what you're doing with the data?

Added Benefit

Who wants to create test data? Well, if your users really care about the system, then they would want to create the test data! This is a great collaborative exercise where admins, developers and users can all learn from each other by reconciling how the system was intended to be used and how the system is actually used.

Developers can setup the spreadsheets and then get functional users to fill in the test data! This frees up the developer's time to focus on the important part that only (s)he can do: actually writing the unit test methods.

Thursday, October 13, 2011

JavaScript Hack to Disable Fields for Internal WebCenter Users

In ApplyYourself, it may sometimes be desirable for some application fields to be editable by the applicant and then locked down for internal WebCenter users after the application is submitted. One example may be the term to which the applicant originally applied, before a decision or subsequent deferral occurs.

In order to accomplish this, the following JavaScript hack may be used.

<script type="text/javascript">
function isViewedInWebCenter() {
  return document.domain == "webcenter.applyyourself.com";
} // function isViewedInWebCenter()

if (isViewedInWebCenter()) {
  var inputElement =
      document.getElementById(inputElementId);
  inputElement.disabled = true;
} // if (isViewedInWebCenter())
</script>

Adding this bit of code to the bottom of a section's HTML should do the trick.

Best Practice for Automated Task Creation?

In retrospect, I think a best practice for automating task creation in Salesforce is to use workflow rules instead of Apex triggers. The primary reason is that the tasks are easier to manage and can be avoided during a mass update is the user leaves the "trigger workflow" checkbox unmarked in the import wizard.

Trimming Whitespace in XSLT

After some digging with Google and a hit on the W3C website, it appears that the String.trim() equivalent in XSLT is normalize-space().

"XQuery 1.0 and XPath 2.0 Functions and Operators (Second Edition)." W3C.

Thursday, October 6, 2011

What Programs and Products Mean in Higher Education

The word "program" is very interesting in its application to higher education. If you ask any higher education administrator or staff member whether his or her institution offers programs, 99% (if not 100%) of the time you'll get a quick and confident "yes". But dive a little deeper and start asking how programs fit into the scheme of CRM, especially within the Salesforce framework, and you may run into a lot of unanswered questions (depending on which institution you're at).

At the crux of the confusion: What exactly is a program?

Merriam-Webster provides one definition of the word "program" as "a plan or system under which action may be taken toward a goal"; Merriam-Webster also provides an alternative definition as simply "curriculum". Great, now what?

Translated into higher education lingo, a program may be defined as a curriculum of study under which a student may enroll in classes with the goal of earning a degree or certification. In the world of enrollment, a program boils down to just another tool to entice people to purchase the products sold by a school, college or university.

At this point, another question may arise: Aren't programs the same thing as products in higher education?

The answer is "no", unless someone can give a convincing argument to support a different position. Products in higher education are the courses offered by an institution. Some may argue that students buy degrees, not courses. "A student comes to us saying that he wants a Bachelor of Science degree with a major in Accounting; the student doesn't come to us saying that they just want to take ACCT 101 and ACCT 102." This argument sort of makes sense but misses the mark in describing the true business relationship between institution and student. In reality, students buy courses and get degrees when enough courses are purchased and completed at an established standard of achievement.

So, in short, courses are the products, not programs. Programs exist to get customers to buy more products, which in the case of higher education are courses.

Armed with these two answers to two high-level questions, the conversation can now move on to how to setup this business model in Salesforce for effective CRM.

Thursday, September 8, 2011

Apex Data Loader for Mac, Linux and Unix

Salesforce had the foresight to code the Apex Data Loader (ADL) in Java, which is a cross-platform tool that should work on any computer that has a JRE installed. This implies that the ADL should work on all OS's, not just on Windows.

Force 201 made a great post about running ADL with a GUI, but I just wanted to know how to run the ADL in batch mode from a command line (so that I can script the process).

It turns out to be as easy as the following:
  1. Download and install the Apex Data Loader on a Windows computer.
  2. Copy the files in "%ProgramFiles%\salesforce.com\Apex Data Loader apiVersion" to the desired directory on the target computer or server. Use binary mode for data transfer when applicable.
  3. Setup the batch mode configuration files as you normally would. This generally includes config.properties and process-conf.xml.
  4. Look at "\bin\encrypt.bat" to figure out how to create an encrypted password if you need one.
  5. Look at the last few lines of "\bin\process.bat" in the folder to figure out what syntax to use to run ADL via the command line.

For example, with ADL 22.0 on Linux, I can change to the base directory containing all of the ADL files and run this command:
java -cp DataLoader.jar -Dsalesforce.config.dir=conf com.salesforce.dataloader.process.ProcessRunner process.name=classMeetingExtractProcess

classMeetingExtractProcess is the id of one of my beans defined in process-conf.xml.

Saturday, August 20, 2011

Merge Field Functions in Email Templates

This may help to get people thinking about how much can be accomplished with text and HTML email templates before diving into Visualforce templates.

Did you know that you can substitute in text for blank values? By using the {!sObject.FieldName, substitute text including spaces} convention, blank values can be automatically replaced with default values.

Furthermore, it looks like at the email templates recognize at least two Visualforce-esque functions, demonstrated in the following examples:
  • {!NullValue(Lead.Name,"visitor")}, which is the same as the substitution described above
  • {!If(Lead.Name="Barack Obama","President","Citizen")}
  • {!If(IsPickVal(Lead.Status,"New"),"Super Important","Somewhat Important")}
Does anyone have any other information on what functions can be used in simple text or HTML email templates? Are all Visualforce functions available here, I wonder?

Tuesday, August 16, 2011

Reliably Getting Record Type ID's for Test Classes

I'm going to share this simple way of reliably getting a record type ID in Salesforce for testing purposes. Using this method will guarantee that the right record type is retrieved for the right object.

All one has to do in the test class is... get ready for the ridiculously long line of code...

private static final Id RECORD_TYPE_NAME_RECORD_TYPE_ID =
Schema.SObjectType.sObjectApiName.getRecordTypeInfosByName().get('Record Type Name').getRecordTypeId();


Thank you, Salesforce, for sObject Describe Results.

Saturday, August 13, 2011

Mixing Person Accounts, Contacts and Business Accounts

I really enjoy the excitement of learning new things, especially through active discussion and target questions and answers. One of the subjects that never lets me down in this area is the concept of person accounts in Salesforce.

Having raved about it in previous posts, I feel obliged to answer a question that was posed about how to actually use person accounts and business accounts in the same org.

The answer could be fairly straightforward: In the standard sales processes, there's no need to mentally make a big distinction between the two. Opportunities are tied to accounts, not to contacts. This means that if a salesperson is trying to close a deal, it doesn't matter if the deal is for a person or for another organization. The opportunity is what's important, and it's always related back to the account entity, person or organization, with which you're doing business.

A trickier question which we're running into at work is: What's the best way to handle people who are both your direct customers and who are contacts at other organizations? Theoretically, the idea of linking the contact to both the business account and to the person account would work. In practice, however, I think if an organization is investing time and energy into this level of constituent mapping, then it would be worthwhile to analyze the business needs and maybe setup some workflow, Apex and Visualforce to take advantage of the data links.

Not to dodge the question entirely, but I think that there are always specific organizational needs that drive people to setup more complex and complete data models. If anyone's interested in discussing a specific use case, I'd be totally game!

Thursday, August 11, 2011

Trigger Context to Provide Related Record Information

Developers who have worked with triggers may already know that related object information is not available in the context of a trigger. To elaborate on the problem, I'll give an example.

Take the Contact object. If I write a trigger on the Contact object, I'll have access to the AccountId value for each Contact handled by the trigger, but I will not have access to the related values such as Account.Name or Account.Owner.Name. Usually, to get around this, I would need to write SOQL within the trigger to retrieve the related values for use within the trigger.

This has personally been a great pain in my side, and it only seems to grow more intense as time goes by. Now, I'm stuck at trying to figure out how to get at those related values in another class that's used by the trigger. After some frustration, I came up with an idea.

What if I used a static variable stored in a utility class that I could set and get at will, wherever as long as I'm within the same trigger context? Well, that'd be pretty cool. To that end, I arrived at the concept for a TriggerContext class.

I won't spend too much time discussing it, as a demo's worth a million words. The classes and triggers inside TriggerContextDemo-1.0.zip can be added to any sandbox or developer org. To see the effects, simply create a new contact record, save it, and then update it by associating an account.

As I'm wrapping up this post, I realize that another, more straightforward approach to handling this could be to have the trigger's associated utility class automatically assign the related records back to the trigger records by writing to the sObject fields and not to the record ID fields. I'll look into this more tomorrow as I try to handle related values in a real project.

Tuesday, August 9, 2011

The Ingenuity of Person Accounts

As I meditated more on the idea in my previous post about people's connections to different accounts, a surprising realization occurred to me: We don't even have to engineer a new object; Salesforce has already done this for us! And the answer is... Person Accounts!

The basic implementation can be done in two simple steps:
  1. Activate the Person Accounts feature.
  2. Create Person_Account__c as a Lookup(Account) field on the Contact object

Conceptually, the idea is simple: The person is the core piece of the puzzle. Take John Doe, for example. John can be a contact for Acme Corporation and simultaneously be a contact for Zenith, Inc. All that's needed to represent these relationships are a person account record for John, two business account records for Acme and Zenith, and two contact records to link John to the two accounts.

Although I haven't thought this next idea in much more depth, I have an inkling that this model could potentially even replace the Nonprofit Starter Pack's Household object with a "Household Account" record type. Wouldn't that be a beautiful alignment of developer resources? All of Salesforce's main development efforts on Contacts and Accounts could then directly benefit all users in every industry. No more waiting for updates to the NPSP Householding package; just wait for the next seasonal release of Salesforce.

Friday, August 5, 2011

People, Not Contacts and Affiliations, Are the Way to Go

Poking around the nonprofit space today, I came across the Affiliations for the Nonprofit Starter Pack app on the AppExchange. I installed it, and all it ended up doing was creating a new object called Affiliation that links contacts with accounts. Ugh... I sense a very unpleasant asymmetry here, where a contact record can be related to an account either directly through the Account field or through an affiliation record sandwiched between the contact and another account.

While this structure definitely works to get us through most of our work, is this really the right way forward? If people at the Saleforce Foundation are pouring time and energy into developing something for the greater good, wouldn't it be great if we are assured that their focus is on the right vision?

Regarding affiliations, I don't think the Affiliation object is the right vision.

Here's a thought: Contacts were originally defined as people of interest related to an organization. Somehow, we diverged from this model by redefining contacts as people with a primary connection to a specific organization via the Lookup(Account) field. What's the difference, you ask? The difference is that in the original definition, a Contact record is the link between a person and an account; in the world with Affiliation as a junction between Contact and Account, we've redefined a Contact as the person, which it was never meant to be.

So, by introducing the Affiliation object, we've bastardized the idea of a "contact". How do we pick which Contact record stays with its account? How do we choose between a creating new Contact record and creating an Affiliation record? Is it better to have my Contact record as a child of account A with an affiliation to account B, or the other way around?


The answer: For orgs that want to map out people's relationships to different Account records, introduce the Person object. Then, link every Contact record to the appropriate Person record, and you have the magic sauce that paints the real picture of a person and his or her affiliations.

Monday, August 1, 2011

Removing ^M Characters from File with vi

So, I transferred a file from Windows to a Unix server, and when I opened the file with vi (not vim) on my server, I was confronted with endless ^M characters.

How do I get rid of these characters?

Basically, after some fumbling around on the internet, I determined that I needed to use the following command.

:1,$s/^M//g

To type the ^M character, press Ctrl+V for the ^ and then Ctrl+M to get the M afterward. vi apparently does not recognize the ranged specified by %, so I had to use 1,$ instead.

Friday, July 29, 2011

Efficient Regex Pattern for Getting Hashtags

After digging around the Internet for a while and not finding a regex pattern that was able to produce all of the hashtags in a String, I finally created my own based on information I gathered from a few other places.

\B#[a-zA-Z][a-zA-Z0-9]+

My sources include the following:

I took this information and created a method in Salesforce to grab all of the hashtags from a String and return it in a Set, as shown below.

/**
 * Get the Set of hashtags (including
 * the '#' character) used within a String in
 * all lower case, for ease of comparison.
 *
 * @param  text The String text to analyze.
 * @return      The Set of hashtags
 *              used within the text.
 */
public static Set getHashtagSet(
        String text) {
    
    // Instantiate the resulting set.
    
    Set hashtagSet = new Set();
    
    // Only look for hashtags if text is given.
    
    if (text != null) {
        Pattern hashtagPattern = Pattern.compile(
                '\\B#[a-zA-Z][a-zA-Z0-9]+');
        Matcher hashtagMatcher =
                hashtagPattern.matcher(text);
        
        while (hashtagMatcher.find()) {
            hashtagSet.add(
                hashtagMatcher.group().toLowerCase());
        }   // while (hashtagMatcher.find())
    }   // if (text != null)
    
    // Return the results.
    
    System.debug('hashtagSet = ' + hashtagSet);
    
    return hashtagSet;
}   // public Set getHashtagSet(String)

Tuesday, July 19, 2011

Navigation Extension for Salesforce Sites

Thinking about how to best handle navigation within Salesforce Sites, I decided to try my luck with setting up an easily re-usable extension that can be applied to an entire site.

Fortunately, it looks like the apex:commandLink element paired with apex:param is able to produce the customizable navigation code I wanted.

Instead of writing out methods like the following...

public PageReference goToPage1() { ... }
public PageReference goToPage2() { ... }
public PageReference goToPage3() { ... }

all that's needed is...

public PageReference goToDestinationPage() { ... }

Much better, right? See the demo source code for more details.

Monday, July 18, 2011

Preview as Admin Feature for Salesforce Sites

I just discovered (1 year too late) the Preview as Admin feature for developing Salesforce Sites.  This has got to be one of the more useful things that I wish I had picked up earlier.

Basically, when developing a site and testing it as an anonymous user, error messages are sometimes hidden behind the annoying "Authorization Required" message.

Preview as Admin saves the day by delivering the anonymous user experience, enhanced with a useful description of the errors at the bottom of the page whenever errors appear.

Awesome, indeed.  I wonder what other features are out there that I don't know but should know...

Friday, July 15, 2011

Settings to Produce YouTube-compatible Video with Expression Encoder 4

After some fiddling around, I figured out some simple settings that can be used to produce a YouTube-compatible video captured with Expression Encoder 4 Screen Capture and encoded with Expression Encoder 4.

Note: I started with the "VC-1 High Speed Broadband CBR" preset.

Video settings:

  • Mode = "CBR - 1 pass"
  • Bitrate = 1024 Kbps
  • VC-1 Settings...
    • Video Complexity = "Fastest (0)"
Audio settings:
  • Mode = "CBR - 1 pass"
  • Bitrate = "32 kbps"

Lead and Contact Merge Fields in Email Templates

Interesting note about Lead and Contact merge fields in Salesforce email templates: Some of the fields will populate regardless of whether the record is of the correct type.

For example, in my template, the first line read:
Dear {!Contact.FirstName}{!Lead.FirstName},

I figured that only one of the two fields would actually produce the target's first name. However, when I actually tested the merge fields in Salesforce with a Contact record and with a Lead record, in both instances the result read:
Dear JohnJohn,

Interestingly, this duplicate merge did not occur in the signature, where I had the following:
Sincerely,
{!Contact.OwnerFullName}{!Lead.OwnerFullName}

In the signature, only the relevant OwnerFullName field was populated, depending on whether a Contact or a Lead was selected.

I wonder whether this phenomenon is intentional or indicative of a bug...

Where Are My Packages?

After a ridiculously lengthy search on the Internet for guidance on finding the location of packages installed with apt-get, I finally stumbled on the solution: "See Where a Package is Installed on Ubuntu".

Basically, the command I needed was:
dpkg -L packagename

Friday, May 27, 2011

JavaScript Hack to Add All Fields to Export Template

Faced with the daunting task of creating an export template in ApplyYourself that included all available fields, I decided to try my luck at using JavaScript to do the job for me (with greater accuracy).

The end result is the following hack, which adds all exposed nodes to the template.
javascript:

/* Grab the fields frame. */

var fieldsFrame =
    document.getElementsByName(
        "frameQuestionsTree")[0];
var fieldsWindow = fieldsFrame.contentWindow;
var fieldsDocument = fieldsWindow.document;

/* Grab the actions frame. */

var actionsFrame =
    document.getElementsByName(
        "frameActions")[0];
var actionsWindow = actionsFrame.contentWindow;
var actionsDocument = actionsWindow.document;

/* Find the exposed nodes to add. */

var fieldsATags = fieldsDocument.getElementsByTagName("a");
alert(fieldsATags.length + " A tags found in fields window.");

var nodeIdPattern = /Nod[0-9]+/;
var nodeATags = new Array();
for (var i = 0; i < fieldsATags.length; i++) {
  var fieldsAElem = fieldsATags[i];
  var fieldsAIdAttr = fieldsAElem.attributes["id"];
  if (fieldsAIdAttr) {
    if (fieldsAIdAttr.value.match(nodeIdPattern)) {
      var nodeNumText = fieldsAIdAttr.value.slice(3);
      fieldsWindow.setSelectedNode(parseInt(nodeNumText));
      actionsWindow.BtnAddField_onclick();
    }
  }
}
This hack was tested in...
  • Chrome 11 on Windows 7
  • Internet Explorer 9 on Windows 7

Tuesday, May 24, 2011

JavaScript Hack to Set Visibility for Field-Level Security

Having to click numerous (sometimes over 100) checkboxes just to grant or revoke visibility to certain fields via Field-Level Security settings can be a pain. Here's a JavaScript hack that can mark all of the "Visible" checkboxes on the Field-Level Security edit page.

The following code was tested in Firefox 4 and Chrome 11 on Windows 7. It can probably be adapted easily to work for marking the read-only checkbox and for clearing checkboxes as well.

javascript:

/* Locate the form. */

var flsForm =
    document.getElementById("editPage");

/* Find the form inputs. */

var inputs =
    document.getElementsByTagName("input");

var visibleInputs = new Array();
for(var i = 0; i < inputs.length; i++) {
  var titleAttr = inputs[i].attributes["title"];
  if(titleAttr != null) {
    if(titleAttr.value == "Visible") {
      var visibleInput = inputs[i];
      visibleInputs[visibleInputs.length] =
          visibleInput;
    }
  }
}

/* Mark all checkboxes. */

for(var i = 0; i < visibleInputs.length; i++) {
  var eName =
      visibleInputs[i].attributes["name"].value;
  
  var e = flsForm.elements[eName];
  
  if(!e.checked)
    e.click();
}

Wednesday, May 18, 2011

JavaScript Hack to Mass Delete Custom Picklist Values

As an extension of last night's efforts, I've created what appears to be a working JavaScript hack to delete all picklist values from a custom picklist. This simplifies maintenance of picklist values by allowing an admin to delete all the values and then simply re-add the ones that are desired.

The following code was tested in Chrome 11 on Windows 7.

javascript:

/* Gather all of the a elements on the page. */

var links = document.getElementsByTagName("a");

/* Pick out the Del links. */

var delLinks = new Array();
for (var i = 0; i < links.length; i++) {
  var link = links[i];
  
  if (link.innerHTML == "Del") {
    /*alert("Del link found!");*/
    /*alert(link.attributes['href'].value);*/
    delLinks[delLinks.length] = link;
  }
}

/* Open each Del link to delete the associated
   picklist value.
   
   This code can be augmented as desired
   to only delete certain values.
   
   However, for custom picklists it's probably
   easier to just delete all of the values
   and then re-add the desired values. */

for (var i = 0; i < delLinks.length; i++) {
  var delLink = delLinks[i];
  window.open(
      delLink.attributes['href'].value);
}

As with all hacks, please to use with caution, at your own risk.

Tuesday, May 17, 2011

Mass Deleting Picklist Values

Apparently, the only way one can (relatively) easily mass delete picklist values is by editing the object definition in the Force.com IDE.

To do this:
  1. Open the object definition (e.g., Contact.object).
  2. Delete the picklistValues elements that are no longer desired.

I had to prune a list of 485 picklist values in a language selection picklist to a more reasonable number.

Regardless of this workaround, it would be great if this feature was more easily accessible through the web UI, as per the following idea: "mass delete picklist values (setup)"


Apparently, all I succeeded in doing was shifting the order of the picklist values. At this point I am also at a loss as to how to mass delete picklist values, much to my chagrin.

However, I did eventually create a "sort of" workaround: "JavaScript Hack to Mass Delete Custom Picklist Values"

Thursday, May 12, 2011

Increasing Speed of Command Line Extract/Export with Apex Data Loader

I had a strange problem when I tried to automate a routine, daily export that I was performing with the Apex Data Loader (version 20.0 and 21.0): Exporting through the GUI only took 5 seconds, but exporting through the command line took anywhere from 30 minutes to 1 hour.

After 4 weeks, Salesforce Premier Support finally delivered a solution to the case I logged: Set the sfdc.debugMessages value to false. I'm very glad that Premier Support was able to find a simple solution to this problem.

Apparently, debug messages were so verbose for every record being exported that operation time increased by a ridiculous factor. Luckily for me, debug messages are not that important for an export.

Saturday, May 7, 2011

Bug (?) with ISNULL() Function in Validation Rules

I tried to setup a simple validation rule today in my sandbox to enforce the presence of a value in a Lookup field.

My first attempt for the error formula was:
ISNULL( Source_Program__c )

However, this turned out not to work at all. Whether a value was in the field or not, the formula never generated a validation error.

Then, I switched over to an even more basic formula:
Source_Program__c = null

This worked like a charm. Am I missing something here?

Friday, April 29, 2011

keeping Person Accounts on the roadmap

From a discussion on Person Accounts yesterday, a Salesforce Foundation representative said about Person Accounts, "we do not have a roadmap going forward but the current offering is going to continue". What does this mean to us as Salesforce customers and prospective customers? One school of thought says that having no roadmap indicates that the feature is on its way out. Another school of thought says that having no roadmap simply means that at the current time, the feature meets most if not all of users' needs and has no need to be on a roadmap.

But let's stop for a second and think, "Why is this feature important?" My thought on that is, "Should a database be structured to match the business model?" To me, the entities with which we do business dictate whether we should use Person Accounts or not. If we contract and transact with other businesses, then accounts with contacts would work. If we contract and transact with individual people, don't person accounts make more sense? A simple use case is managing individual donors and contributors for nonprofits. Another use case is managing students at an institution of learning.

But perhaps most importantly, what are some of the pros and cons of using Person Accounts? I'll admit that I'm probably biased because we use Person Accounts in two of our orgs, but I hope not to lie when I say that I would love to know all of the benefits and liabilities of using this feature.

Below are starters taken from an editable document shared with the public, in lieu of something more authoritative from Salesforce.

Some pros:
  • The model fits the B2C way of doing business.
  • You can contract with people instead of businesses using the standard Contract object.
  • Person accounts will show up on both account and contact reports, which would be accurate for a company that mixes B2B with B2C.
  • Person accounts can leverage both Account fields and Contact fields via a single sObject.
  • Contact fields are directly accessible for a Person Account via Apex through a single Account instance.

Some cons:
  • To convert a lead into an opportunity for a new person account, the lead cannot have a value in the standard Company field.
  • For person accounts, formula fields at the account level cannot reference contact fields.
  • Code may have to account for the fact that there are now two record types on the Account object.

Personally, I believe that Person Accounts make a great selling point and feature for the Salesforce products. If being off the roadmap is a fault, then maybe we just need to promote some of the ideas on the IdeaExchange that will make the feature even stronger and more compelling:

Tuesday, April 26, 2011

Bug (?) with Rerendering an apex:pageBlockSection When apex:inputFile Exists Elsewhere on Page

I can understand throwing an error if we rerender part of a page that contains an apex:inputFile component, but I'm a bit confused about why rerendering part of a page that has no apex:inputFile component should generate the following error:
apex:inputFile can not be used in conjunction with an action component, apex:commandButton or apex:commandLink that specifies a rerender or oncomplete attribute.

I've created a demo for getting the error message, although you can't see the exact error message unless you deploy the page in your own sandbox. The source code can be downloaded here.

What I expected was that my partial page refresh of "Dependent Section" would work just fine. However, when I change the value in my controlling picklist, the page just fails, with the error mentioned above.

Is this a bug? I'm not sure what Salesforce would say, but it certainly is puzzling and extremely frustrating to me.

Tuesday, April 12, 2011

DML error: Name not specified

When trying to upsert a list of records via Apex, I received a slew of "Name not specified" errors on every single one of the records.  The Database.Error.getStatusCode() instance method returned "MISSING_ARGUMENT", and the Database.Error.getMessage() instance method returned "Name not specified".

My upsert was constructed as follows:
upsertResults = Database.upsert(applications,
        Application__c.Unique_External_Application_ID__c, false);

Application__c is a custom object, and Unique_External_Application_ID__c is a custom field that is set to be an external ID. applications is my List that I was trying to upsert.

Basically, after a few hours of frustration and asking for help on #salesforce, I determined that the error came from the fact that I was trying to set a Lookup field by specifying an external ID on the related object. An example is shown below:
Application__c application = new Application__c();
application.Start_Term__r = new Term__c(Banner_Code__c = '201115');

I had simply assumed this would work, since I knew that a lookup field can be set by setting the custom field __r to new sObject(Name = recordName). Apparently I was 100% wrong.


But I did create an idea out of this whole mess: enable Apex DML to set Lookup or Master-Detail by external ID

I wish I could recall the idea, because apparently the error was the result of my putting a null value into the Name field when specifying the related record. Essentially, my code was trying to execute the following:
application.Program__r = new Program__c(Name = null);

Specifying an external ID in the following manner does work:
application.Term__r = new Term__c(Banner_Code__c = '201115');

Monday, March 21, 2011

Bug (?) with apex:actionSupport Containing apex:param

I'm encountering some unexpected behavior with apex:actionSupport and apex:param in what I thought would be a simple application.

I've created a demo of an apex:select with an apex:actionSupport component to illustrate the problem.  The source code for the demo is also available.

What I expected to happen:

  1. Change the picklist value.
  2. Observe that assignToColor matches picklistColor.
  3. Observe that displayColor matches picklistColor.

What actually happens is that nothing changes except picklistColor.

Is this a bug or is this expected behavior?  I thought that apex:param could be used with apex:actionSupport components.

Sunday, March 20, 2011

Bug with Custom Component Rerender inside apex:repeat

I seem to have run into a bug with custom components trying to rerender parts of themselves: The rerendered component is able to display updated values as text, but formula evaluations of any updated values ignore the updated values and instead use initial values.

For example: Let's say I have a component has an apex:inputText with value="{!accountName}" that rerenders the parent apex:outputPanel during an onchange event. After rerendering, a few odd things happen:

  • The apex:inputText reverts to blank control.
  • If I display {!accountName} as text, the new value appears fine.
  • Any other components that have rendered="{!NOT(ISNULL(accountName))}" are still not rendered.

An experience is worth a million words, so I've put up a demo in my sandbox.  The source code for the demo is also available.

  1. In the Lead (standalone) section, enter something into the Name field.
  2. Click somewhere else on the page.  An Ajax refresh will occur, and the Text Insertions and Output Component Renders section should correctly update to reflect the new Name value.
  3. Now, in any Lead (from List) section, enter something into the Name field.
  4. Click somewhere else on the page.  An Ajax refresh will occur.
  5. Observe two problems:  The Name field is blanked out, and the Output Component Renders section did not change.

Hopefully, Salesforce Premier Support will tell me that the problem will be fixed immediately within the next 5 business days.  Hopefully...

Comparison of null to Integer Always Returning true

I ran across an interesting phenomenon when comparing null values to Integer values on Visualforce pages: The comparison always returns true!

  1. null < 0 evaluates to true
  2. null = 0 evaluates to true
  3. null > 0 evaluates to true

It appears that it's best not to compare null values to Integers, since the result is effectively useless.

ISBLANK(String) Returning false When String.length() Returns 0

I discovered this with version 21.0 of the Salesforce API: When a String's length is 0, the ISBLANK(String) Visualforce function will actually return false instead of true.

Visualforce Developer's Guide, Version 21.0 describes the ISBLANK function as follows:

Determines if an expression has a value and returns TRUE if it does not. If it contains a value, this function returns FALSE.

A search for ISNULL in the documentation returned no real results, which makes me wonder if ISNULL has been unofficially deprecated in favor of ISBLANK.

So, it appears that the only way to know for sure whether a String input has been blanked out is to use the length method and compare it to 0.

Saturday, March 19, 2011

apex:define Tag Precedence in Visualforce Templates

I learned something interesting through trial-and-error yesterday:

If you define a set of templates where each successive template "extends" the previous template, the apex:insert tag in all previous templates are available for definition in the final implementing page.

For example, let's take take the following hierarchy of templates:
  1. SiteMasterTemplate
  2. SiteDepartmentTemplate--contains an apex:composition with template="SiteMasterTemplate"
  3. SiteProductTemplate--contains an apex:composition with template="SiteDepartmentTemplate"

Now, if we create a page called SiteSuperMotor that contains an apex:composition with template="SiteProductTemplate", our page can actually use apex:define that define apex:insert elements in SiteMasterTemplate!

Furthermore, let's say that we have apex:insert elements in both SiteDepartmentTemplate or SiteProductTemplate that share the same name attribute as an apex:insert in SiteMasterTemplate, then the SiteSuperMotor page's apex:define would only define the apex:insert in SiteMasterTemplate!

Friday, March 18, 2011

Visualforce Misinterprets Empty xmlns Attributes

For unknown reasons, Visualforce does not handle an empty xmlns attribute inside a DIV tag. Unexpected behavior will result if a DIV tag is used inside a Visualforce page with xmlns="" specified as an attribute.

If a Visualforce page that contains simple HTML is not rendering like the page from which the HTML was copied, checking for empty xmlns attributes may be the key to fixing the problem.

Tuesday, March 8, 2011

jQuery in Custom Input Components

I'm trying to write my own custom input component that updates values based on button clicks. The component idea is similar to the enhanced jQuery input components created by Joel Dietz, although far less universal in application. An example of Joel's components can be found below.

"enhancedText.component"
sfdcjqueryenhancements

"EnhancedTextController.cls"
sfdcjqueryenhancements

One trick to making the component inputs work is to factor in the quirky DOM ID's generated by Salesforce. This is addressed in the following blog post from Wes Nolte.

"VisualForce Element Ids in jQuery selectors"
The Silver Lining

To clarify Wes's post, the function is defined as follows:

function esc(myid) {
    return '#' + myid.replace(/(:|\.)/g,'\\\\$1');
}

Also, the esc function cannot be called with a literal! The String must be stored in a var first, and the var should then be passed to the esc function.

Whew! I'm not done with my custom input component yet, but I definitely see bright rays of hope.

Monday, March 7, 2011

How to Write to a Custom Component Attribute

I spent a long time trying to figure out why the following code was not working:

<apex:component id="this" controller="MySiteInputAccountIdCtrl"
selfClosing="true">

<apex:attribute name="value" type="Id"
description="Account Id to pass back to the page."
assignTo="{!accountId}"
required="true"/>

<apex:inputText id="accountIdIText"
value="{!accountId}"/>

</apex:component>

accountId was a simple property with a vanilla pair of getter and setter methods. When the component is rendered as a component like <c:MySiteInputAccountId value="{!contact.AccountId}>, contact.AccountId would not update no matter what I typed into the input field.

Several hours later, I took a look back at a custom component that worked (and was also created by me after a similar bout of confusion)... And it turns out that I was writing the input to the wrong object. The correct component markup is as follows:

<apex:component id="this" controller="MySiteInputAccountIdCtrl"
selfClosing="true">

<apex:attribute name="value" type="Id"
description="Account Id to pass back to the page."
assignTo="{!accountId}"
required="true"/>

<apex:inputText id="accountIdIText"
value="{!value}"/>

</apex:component>

I can't believe I forgot this resolution to a 3-hour frustration almost immediately... just to experience a new 6-hour frustration on the same topic. Hopefully writing this down will help me remember my lesson and avoid a third incident.

Friday, February 25, 2011

Conversion Error setting value 'value1 value2 value3' for '#{myMultiselectPicklistValue}'.

Salesforce is great. Apex is great. Visualforce is great.

Until you run into bizarre errors with no obvious explanation, such as the following error:
Conversion Error setting value 'value1 value2 value3' for '#{myMultiselectPicklistValue}'.

This error comes from trying to save the selections from an apex:selectCheckboxes component into a multi-select picklist field. You would think that it's as easy as simply specifying {!property} for the value attribute of the apex:selectCheckboxes component. But, no, it's not.

Multi-select picklist values are stored as String values, with each option delimited with a semicolon.  This is great to know, but what happens with apex:selectCheckboxes? apex:selectCheckboxes expects a List<String>! This discussion board post hints at the annoyance awaiting developers: "Checkboxes not saving".

Basically, to spell it out for myself and for others, here's what we have to do as developers working around this problem.

What we want to write is:

<apex:selectcheckboxes value="{!mPicklistValue}">
... and in the controller ...

public String mPicklistValue { get; set; }

Instead, what we have to write is:

<apex:selectcheckboxes value="{!mPicklistValues}">
... and in the overblown controller ...
private String mPicklistValue;
public List<String> getMPicklistValues() {
    List<String> values = null;
    // Convert mPicklistValue into List of 
    // String values

    if (mPicklistValue != null)
        values = mPicklistValue.split(';');

    return values;
}   // List<String> getMPicklistValues()
public void setMPicklistValues(
        List<String> values) {

    // Convert values into a semilcolon-delimited
    // String value

    if (values == null) {
        mPicklistValue = null;
    }
    else {
        mPicklistValue = '';
        
        for (String value : values) {
            mPicklistValue += value + ';';
        }
    }
}   // void setMPicklistValues(List<String>)

Note the plural name of the custom getter and setter methods. Cheers, indeed.

Tuesday, February 15, 2011

Samsung Galaxy Tab Test-drive

I had the opportunity to check out the Samsung Galaxy Tab for a few days, and I was very excited at the chance to use Android on a tablet to personally see how it stacks up against the iPad and iOS 4.

The short review:  Galaxy Tab + Android fall way, way short.  Samsung, did you pull an ostrich and just ignore the fact that the iPad has already captured the imagination and desire of the world over the past year?

Anyway, where was I...

First, the good, since the good is little:

  • Widgets.  It's nice to see things other than little grids of icons on the home screen.
  • Swype input.  Okay, it's pretty cool and revolutionary.  But what the hell is up with the way the rest of the symbol and numeric inputs are displayed?

Grab the popcorn and beer, because the next part may take a while to read.  The bad:
  • Unreliable Wi-Fi connection that works about 1 % of the time.  Really, Samsung?  Really?
  • Landscape form entry was designed by a masochist.  Who had the bright idea that when we use landscape orientation to enter data on forms that we no longer need to see the form?  Or even the label of the field into which we're typing?
  • Thick form factor.  Not appealing.
  • Typo on the setup screen relating to Wi-Fi.  See if you can find it.
  • 802.1x-secured wireless network did not appear on initial setup but did appear later in Settings.
  • Browser bug where the client gets squished into about the top 1/5 of the screen, making it impossible to change the address, see anything or open new tabs.  Basically, I had to kill the app and relaunch it to get back up and running.  If it's going to crash, just crash.  Don't make it a pain for me to relaunch the app.
  • Do I really need a Gmail account to access the Marketplace?  Is my Google Account attached to my company email address not good enough for Android?
  • And so much more that I couldn't remember it all...

Basically, if I had a choice between having a Samsung Galaxy Tab or having nothing at all, guess what I would choose...  Hint:  It's not having the Galaxy Tab.

Thursday, January 20, 2011

String Keys for Apex Maps Are Case-sensitive

I discovered today that String keys for Apex Maps are case-sensitive. Apparently the Strings "johndoe" and "JohnDoe" are not equal when being compared to see if the Map contains a key.

Tuesday, January 18, 2011

Comparing Salesforce Record ID's in Visualforce

Strangely enough, the Visualforce comparison operator does not work when comparing a 15-digit record ID with an 18-digit one. The workaround I concocted is to use a very specific Boolean property in my controller extension to perform the comparison instead, since I really wanted to avoid using any kind of custom ID conversion code that may become invalid in future releases.

public Boolean isCitizenshipCountryUnitedStates {
get {
return stdCtrler.getRecord().get('Citizenship_Country__c') == unitedStatesCountryId;
}
}

The situation I have is that I want to compare the Country of Citizenship specified on an application record (for custom object Application) to see whether the applicant is a U.S. citizen. Country of Citizenship is a Lookup(Country) field, with Country also being a custom object.

Without hardcoding the Id for the United States country, I defined a unitedStatesCountryId property as follows:

 public Id unitedStatesCountryId {
get {
if (unitedStatesCountryId == null) {
List matchingCountries =
[SELECT Id FROM Country__c
WHERE Name = 'United States'];

if (matchingCountries.size() > 0) {
unitedStatesCountryId = matchingCountries.get(0).Id;
}
}

return unitedStatesCountryId;
}

set;
}

When I evaluate the expression {!Application__c.Citizenship_Country__c = unitedStatesCountryId} in my Visualforce page, the expression always returns false. When digging a little deeper, I found that Application__c.Citizenship_Country__c was returning 'a0DT0000006NIDM' while unitedStatesCountryId was returning 'a0DT0000006NIDMMA4'! Visualforce appeared to be comparing the two values as Strings and not as Ids.

Finding no Visualforce-native solution, I had to go the route of creating the isCitizenshipCountryUnitedStates property in my controller extension. Needless to say, this quirk in Salesforce does not make me happy.

Tuesday, January 4, 2011

Deleting Characters (a.k.a. Backspacing) through SSH in PuTTY

I've been annoyed consistently by a quirk with SSH, PuTTY, Unix and sudo where pressing the backspace character prints a ^? on the terminal instead of deleting the previous character. Then, to my amazement today, I stumbled upon a way to delete characters through SSH in PuTTY without messing with any terminal configurations!

To delete characters, all one has to do is press either...
  • Shift+Backspace; or
  • Right Arrow
I assume this works with Terminal in Mac OS X or Linux as well, but I have not yet tried. Gone are the days now when I have to retype a 80+ character command just because of a single, silly typo...