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...

Monday, December 13, 2010

Enforcing Record Name Convention in Salesforce without Apex

One can enforce a record naming convention with a few steps in Salesforce, sans Apex!
  1. Create a formula field called Expected Name with a formula that evaluates to the name you want to see.
  2. Create a validation rule called "Name Expected" with error formula: Name != Expected_Name__c

Enforcing Record Name Uniqueness in Salesforce without Apex

I discovered today that it is possible to enforce record name uniqueness in Salesforce without using any Apex code!

All that is needed is a simple validation rule with the error formula set to the following:
Name = VLOOKUP( $ObjectType.Object.Fields.Name , $ObjectType.Object.Fields.Name , Name ) && Id <> VLOOKUP( $ObjectType.Object.Fields.Id , $ObjectType.Object.Fields.Name , Name )

However, there doesn't seem to be an easy to to perform a case-sensitive comparison in the formula builder. Another thought would be to do the following to enforce name uniqueness:
  1. Create a text field called Unique Name on the object, and add the case-sensitive uniqueness constraint.
  2. Create a workflow field update called Unique Name that just sets Unique_Name__c to Name.
  3. Create a workflow rule called Set Unique Name that's triggered every time a record is created or updated.
  4. Activate the workflow rule.

Last edited on 8/10/2011.