Overload Apex Class to be Controller AND Extension

July 24, 2009 · Filed Under Apex, Development, Visualforce, X-Squared On Demand · 5 Comments 

Wow - today brought an interesting discovery. Here's the situation:

Coding the new premium version of Mass Update Contacts (details to come), I replaced the two parts of the page with Apex Components. This will allow the app to support custom address fields and international address formats.

I didn't want to write one ControllerExtension for the main page, a CustomController for the view section component, and another CustomController for the pageblocktable component. So here is the overloaded class constructor. Note that this works because an extension passes the StandardController to the constructor, and a CustomController passes nothing:

public with sharing class VersatileClass {

private Account account;

public VersatileClass(){
	system.debug('OPERATING AS CONTROLLER');
		if(System.currentPageReference().getParameters().get('id')==null){
			//Include error checking here
		} else{
			string AId = System.currentPageReference().getParameters().get('id');
			account = [select id, name from Account where id = :AId];
			//And whatever else you want to do
		}
}

public VersatileClass(ApexPages.StandardController controller) {
	system.debug('OPERATING AS EXTENSION');
		if(System.currentPageReference().getParameters().get('id')==null){
			//Include error checking here
		} else{
			this.account = (Account)controller.getRecord();
			//And whatever else you want to do
		}
	}
}

Enjoy! This should save people a lot of time.

Email Inbox Version 2

July 15, 2009 · Filed Under Apex, Development, Visualforce, salesforce.com · 4 Comments 

Well, it took about ten minutes from the release of Email Inbox Version 1 for people to request additional features, screenshots, etc. Version 2 includes some neat icons, and this post has the multiple-times-requested screenshots we promised.

First, a teaser screenshot. (There's a better one below the code.)

EmailInbox screenshot

Here's the updated Visualforce code:

<apex:page controller="EmailMessageController" action="{!ViewData}">
    <apex:sectionHeader title="Email Messages" subtitle=""></apex:sectionHeader>
    <apex:pageblock id="emailblock">
        <apex:facet name="header">
            <apex:form >
                <apex:panelGrid styleClass="list"
                    columnClasses="pbTitle,pbButton,pbHelp" columns="3" border="0"
                    cellpadding="0" cellspacing="0">
                    <apex:outputLabel ><h3>Messages</h3></apex:outputLabel>
                    <apex:commandButton value=" Refresh " styleClass="btn"
                        action="{!ViewData}" rerender="emailblock"></apex:commandButton>
                    <apex:SelectList value="{!wheretext}" size="1"  id="controllerselectlist">
                        <apex:actionSupport event="onchange" action="{!ViewData}"
                            reRender="emailblock"></apex:actionSupport>
                        <apex:selectOptions value="{!views}" />
                    </apex:SelectList>
                </apex:panelGrid>
            </apex:form>
        </apex:facet>
        <apex:form >
            <apex:pageblocktable value="{!Messages}" var="e" id="emailtable"
                bgcolor="#F3F3EC" styleClass="list" rowClasses="dataRow"
                onRowMouseOver="hiOn(this);" onRowMouseOut="hiOff(this);">
                <apex:column >
                    <apex:facet name="header">
                        <apex:commandLink action="{!ViewData}"
                            value="{!$ObjectType.EmailMessage.fields.Subject.label}{!IF(sortExpression=='Subject', 
                            IF(sortDirection='ASC','▼','▲'),'')}">
                            <apex:param value="Subject" name="column"
                                assignTo="{!sortExpression}"></apex:param>
                        </apex:commandLink>
                    </apex:facet>
                    <apex:outputLink value="/{!e.Id}" target="_blank">{!e.Subject}</apex:outputLink>
                </apex:column>
                <apex:column >
                    <apex:facet name="header">
                    {!$ObjectType.Contact.fields.Name.label}
                    </apex:facet>
                    <apex:outputLink value="/{!e.Parent.ContactId}" target="_blank"
                        rendered="{!IF(e.Parent.ContactId != '',true,false)}">{!e.FromName}</apex:outputLink>
                    <apex:outputtext value="{!e.FromName}"
                        rendered="{!IF(e.Parent.ContactId != '',false,true)}" />
                </apex:column>
                <apex:column >
                    <apex:facet name="header">
                    {!$ObjectType.Account.fields.Name.label}
                    </apex:facet>
                    <apex:outputLink value="/{!e.Parent.AccountId}" target="_blank">{!e.Parent.Account.Name}</apex:outputLink>
                </apex:column>
                <apex:column value="{!e.FromAddress}">
                    <apex:facet name="header">
                        <apex:commandLink action="{!ViewData}"
                            value="{!$ObjectType.EmailMessage.fields.FromAddress.label}{!IF(sortExpression=='FromAddress', 
                            IF(sortDirection='ASC','▼','▲'),'')}">
                            <apex:param value="FromAddress" name="column"
                                assignTo="{!sortExpression}"></apex:param>
                        </apex:commandLink>
                    </apex:facet>
                </apex:column>
                <apex:column value="{!e.Status}">
                    <apex:facet name="header">
                        <apex:commandLink action="{!ViewData}"
                            value="{!$ObjectType.EmailMessage.fields.Status.label}{!IF(sortExpression=='Status', 
                            IF(sortDirection='ASC','▼','▲'),'')}">
                            <apex:param value="Status" name="column"
                                assignTo="{!sortExpression}"></apex:param>
                        </apex:commandLink>
                    </apex:facet>
                </apex:column>
                <apex:column value="{!e.MessageDate}">
                    <apex:facet name="header">
                        <apex:commandLink action="{!ViewData}"
                            value="{!$ObjectType.EmailMessage.fields.MessageDate.label}{!IF(sortExpression=='MessageDate', 
                            IF(sortDirection='ASC','▼','▲'),'')}">
                            <apex:param value="MessageDate" name="column"
                                assignTo="{!sortExpression}"></apex:param>
                        </apex:commandLink>
                    </apex:facet>
                </apex:column>
                <apex:column >
                    <apex:facet name="header">
                        <apex:commandLink action="{!ViewData}"
                            value="Inbound/Outbound{!IF(sortExpression=='Incoming', 
                            IF(sortDirection='ASC','▼','▲'),'')}">
                            <apex:param value="Incoming" name="column"
                                assignTo="{!sortExpression}"></apex:param>
                        </apex:commandLink>
                    </apex:facet>
                    <apex:image url="/img/emailInbound.gif" rendered="{!e.Incoming}" />
                    <apex:image url="/img/emailOutbound.gif" rendered="{!NOT(e.Incoming)}" />
                    <!--<apex:outputfield value="{!e.Incoming}" rendered="{!NOT(e.Incoming)}" />-->
                </apex:column>
                <apex:column >
                    <apex:facet name="header">
                        <apex:commandLink action="{!ViewData}"
                            value="Attachment{!IF(sortExpression=='HasAttachment', 
                            IF(sortDirection='ASC','▼','▲'),'')}">
                            <apex:param value="HasAttachment" name="column"
                                assignTo="{!sortExpression}"></apex:param>
                        </apex:commandLink>
                    </apex:facet>
                    <apex:image url="/img/emailHasAttach.gif" rendered="{!e.HasAttachment}"/>
                    <apex:outputfield value="{!e.HasAttachment}" rendered="{!NOT(e.HasAttachment)}" />
                </apex:column>
                <apex:column >
                    <apex:facet name="header">
                        <apex:commandLink action="{!ViewData}"
                            value="{!$ObjectType.Case.fields.CaseNumber.label}{!IF(sortExpression=='ParentId', 
                            IF(sortDirection='ASC','▼','▲'),'')}">
                            <apex:param value="ParentId" name="column"
                                assignTo="{!sortExpression}"></apex:param>
                        </apex:commandLink>
                    </apex:facet>
                    <apex:outputLink value="/{!e.ParentId}">{!e.Parent.CaseNumber}</apex:outputLink>

                </apex:column>
                <apex:column value="{!e.ToAddress}">
                    <apex:facet name="header">
                        <apex:commandLink action="{!ViewData}"
                            value="{!$ObjectType.EmailMessage.fields.ToAddress.label}{!IF(sortExpression=='ToAddress', 
                            IF(sortDirection='ASC','▼','▲'),'')}">
                            <apex:param value="ToAddress" name="column"
                                assignTo="{!sortExpression}"></apex:param>
                        </apex:commandLink>
                    </apex:facet>
                </apex:column>
            </apex:pageblocktable>
        </apex:form>
    </apex:pageblock>
</apex:page>

The Apex controller code has not changed from Version 1.

Here's the screenshot, which includes some emails from around the Salesforce ecosystem:

Incoming (and auto-response) Emails

Incoming (and auto-response) Emails

Things to notice and things we've learned:
  1. Salesforce will search ANY Email field to match an incoming email to a Contact. (nice job, Salesforce!) - We know because Simon Fell's Contact.Email is sfell at salesforce.com, and his Contact.Secondary_Email__c is simon at fell.com in the sandbox. Ditto (with her own email addresses) for Irene Brodt.
  2. Auto-response emails will be included. We turned off auto-response after a few emails came in.
  3. Though the Email Address field is coded just to show the field value, Salesforce includes the Gmail link. (We assume that's because we activated the integration, but we may be wrong. Has anyone not activated that?)
  4. We did not (yet) implement the super Email/Web2Case trigger code that Marco Casalaina posted in the Salesforce Support Blog, but if we had, then every email in the list would be associated with a Contact. (Example: Michael Smith of Force2b.net, who will be a Contact from now on!)
Enjoy!

Visualforce Email Inbox

Sonny Cloward, SysAd at Rainforest Alliance, approached us about writing a Visualforce page to display all incoming emails for a given Case Queue. This led a few interesting discoveries. Here's how we handled the (donated time) project:

First, the page was built upon the template Sam Arjimandi built at Salesforce. Instead of the Account object, we substituted the EmailMessage object. But something didn't work.

Fields have certain attributes:
Property Description
Autonumber The API creates an autonumber.
Create Value for the field can be specified during create using the API.
Defaulted on create When created, a default value is supplied if no other value is specified.
Delete Value for the field can be deleted using the API.
Filter Can be used as filter criteria in a SOQL query FROM or WHERE clause.
idLookup Can be used to specify a record in an upsert() call. The Id field of each object has this property and some Name fields. There are exceptions, so check for the property in any object you wish to upsert().
Nillable The field can contain a null value.
Query The field can be queried with SOQL using the API.
Replicate The value of the field can be replicated using the API.
Restricted picklist A picklist that depends on the value of another picklist for the values it displays.
Retrieve Value of the field can be retrieved using the API.
Search Can be searched with SOSL using the API.
Update Can be updated using the API.

The important one here is "Filter" because (as the documentation states) this allows the field to be used in a WHERE clause. Also, however, (as the documentation does not state) it allows the field to be used in an ORDER BY clause. So all fields on the EmailMessage object that do not allow filtering/ordering had to be presented plainly, without Sam's cool PageBlockTable sorting features. Once this was done, Sonny had some great ideas:

  1. Show the email subject, but make that a hyperlink to the email message itself
  2. Link the Case Number (EmailMessage.ParentId) to the Case (EmailMessage.Parent)
  3. Show the Case Contact (EmailMessage.Parent.Contact.Name), linking to the Contact (EmailMessage.Parent.ContactId)
  4. Show the Case Account (EmailMessage.Parent.Account.Name), linking to the Account (EmailMessage.Parent.AccountId)
  5. Provide filters - Incoming only, Unread only, etc.
You'll see in the Apex Code where we added parent object fields to the SOQL query, and where we used a List to populate the query's filter.
public with sharing class EmailMessageController {
   public String EmailMessage { get; set; }
   private List<EmailMessage> messages;
   private String sortDirection = 'ASC';
   private String sortExp = 'MessageDate';
   public String wheretext;

   public EmailMessageController(){
   	wheretext = '';
   }

   public String sortExpression { get {
        return sortExp;
     }
     set {
       //if the column is clicked on then switch between Ascending and Descending modes
       if (value == sortExp)
         sortDirection = (sortDirection == 'ASC')? 'DESC' : 'ASC';
       else
         sortDirection = 'ASC';
       sortExp = value;
     }
   }
   
public void setWhereText(String value) {  
   whereText = value;
 }
public string getWhereText(){
	return wheretext;
}
   
public List<SelectOption> getViews(){
	List<SelectOption> options = new List<SelectOption>();
	options.add(new SelectOption('WHERE e.id != null','All'));
	options.add(new SelectOption('WHERE e.Incoming = true AND e.Status = \'0\' ','Incoming Unread'));
	options.add(new SelectOption('WHERE e.Incoming = true AND e.Status = \'1\' ','Incoming Read'));
        options.add(new SelectOption('WHERE e.Incoming = true','All Incoming'));
        options.add(new SelectOption('WHERE e.Incoming = false','All Outgoing'));
        options.add(new SelectOption('WHERE e.ToAddress = \'support@x2od.com\'','Support Queue'));
        options.add(new SelectOption('WHERE e.ToAddress = \'support2@x2od.com\'','Support Queue2')); //etc.
        return options;
}

 public String getSortDirection() {
    //if not column is selected 
    if (sortExpression == null || sortExpression == '')
      return 'ASC';
    else
     return sortDirection;
 }

 public void setSortDirection(String value) {  
   sortDirection = value;
 }
  
   public List<EmailMessage> getMessages() {
       return messages;
   }
   
   public PageReference ViewData() {
       //build the full sort expression
       string sortFullExp = sortExpression  + ' ' + sortDirection;
      
       //query the database based on the sort expression
       messages = Database.query('Select e.FromAddress, e.Parent.ContactId, e.Parent.Contact.Name, e.Parent.Account.Name, e.ToAddress, e.Parent.CaseNumber, e.Parent.AccountId, e.TextBody, e.SystemModstamp, e.Subject, e.Status, e.ParentId, e.MessageDate, e.LastModifiedDate, e.LastModifiedById, e.IsDeleted, e.Incoming, e.Id, e.HtmlBody, e.Headers, e.HasAttachment, e.FromName, e.CreatedDate, e.CreatedById, e.CcAddress, e.BccAddress, e.ActivityId From EmailMessage e ' + wheretext + ' order by ' + sortFullExp + ' limit 1000');
       return null;
   }
}

And here's the Visualforce Page:

<apex:page controller="EmailMessageController" action="{!ViewData}">
	<apex:sectionHeader title="Email Messages" subtitle=""></apex:sectionHeader>
	<apex:pageblock id="emailblock">
		<apex:facet name="header">
			<apex:form>
				<apex:panelGrid styleClass="list"
					columnClasses="pbTitle,pbButton,pbHelp" columns="3" border="0"
					cellpadding="0" cellspacing="0">
					<apex:outputLabel><h3>Messages</h3></apex:outputLabel>
					<apex:commandButton value=" Refresh " styleClass="btn"
						action="{!ViewData}" rerender="emailblock"></apex:commandButton>
					<apex:SelectList value="{!wheretext}" size="1"	id="controllerselectlist">
						<apex:actionSupport event="onchange" action="{!ViewData}"
							reRender="emailblock"></apex:actionSupport>
						<apex:selectOptions value="{!views}" />
					</apex:SelectList>
				</apex:panelGrid>
			</apex:form>
		</apex:facet>
		<apex:form>
			<apex:pageblocktable value="{!Messages}" var="e" id="emailtable"
				bgcolor="#F3F3EC" styleClass="list" rowClasses="dataRow"
				onRowMouseOver="hiOn(this);" onRowMouseOut="hiOff(this);">
				<apex:column>
					<apex:facet name="header">
						<apex:commandLink action="{!ViewData}"
							value="{!$ObjectType.EmailMessage.fields.Subject.label}{!IF(sortExpression=='Subject', 
							IF(sortDirection='ASC','▼','▲'),'')}">
							<apex:param value="Subject" name="column"
								assignTo="{!sortExpression}"></apex:param>
						</apex:commandLink>
					</apex:facet>
					<apex:outputLink value="/{!e.Id}" target="_blank">{!e.Subject}</apex:outputLink>
				</apex:column>
				<apex:column>
					<apex:facet name="header">
					{!$ObjectType.Contact.fields.Name.label}
					</apex:facet>
					<apex:outputLink value="/{!e.Parent.ContactId}" target="_blank"
						rendered="{!IF(e.Parent.ContactId != '',true,false)}">{!e.FromName}</apex:outputLink>
					<apex:outputtext value="{!e.FromName}"
						rendered="{!IF(e.Parent.ContactId != '',false,true)}" />
				</apex:column>
				<apex:column>
					<apex:facet name="header">
					{!$ObjectType.Account.fields.Name.label}
					</apex:facet>
					<apex:outputLink value="/{!e.Parent.AccountId}" target="_blank">{!e.Parent.Account.Name}</apex:outputLink>
				</apex:column>
				<apex:column value="{!e.FromAddress}">
					<apex:facet name="header">
						<apex:commandLink action="{!ViewData}"
							value="{!$ObjectType.EmailMessage.fields.FromAddress.label}{!IF(sortExpression=='FromAddress', 
							IF(sortDirection='ASC','▼','▲'),'')}">
							<apex:param value="FromAddress" name="column"
								assignTo="{!sortExpression}"></apex:param>
						</apex:commandLink>
					</apex:facet>
				</apex:column>
				<apex:column value="{!e.Status}">
					<apex:facet name="header">
						<apex:commandLink action="{!ViewData}"
							value="{!$ObjectType.EmailMessage.fields.Status.label}{!IF(sortExpression=='Status', 
							IF(sortDirection='ASC','▼','▲'),'')}">
							<apex:param value="Status" name="column"
								assignTo="{!sortExpression}"></apex:param>
						</apex:commandLink>
					</apex:facet>
				</apex:column>
				<apex:column value="{!e.MessageDate}">
					<apex:facet name="header">
						<apex:commandLink action="{!ViewData}"
							value="{!$ObjectType.EmailMessage.fields.MessageDate.label}{!IF(sortExpression=='MessageDate', 
							IF(sortDirection='ASC','▼','▲'),'')}">
							<apex:param value="MessageDate" name="column"
								assignTo="{!sortExpression}"></apex:param>
						</apex:commandLink>
					</apex:facet>
				</apex:column>
				<apex:column value="{!e.Incoming}">
					<apex:facet name="header">
						<apex:commandLink action="{!ViewData}"
							value="{!$ObjectType.EmailMessage.fields.Incoming.label}{!IF(sortExpression=='Incoming', 
							IF(sortDirection='ASC','▼','▲'),'')}">
							<apex:param value="Incoming" name="column"
								assignTo="{!sortExpression}"></apex:param>
						</apex:commandLink>
					</apex:facet>
				</apex:column>
				<apex:column value="{!e.HasAttachment}">
					<apex:facet name="header">
						<apex:commandLink action="{!ViewData}"
							value="{!$ObjectType.EmailMessage.fields.HasAttachment.label}{!IF(sortExpression=='HasAttachment', 
							IF(sortDirection='ASC','▼','▲'),'')}">
							<apex:param value="HasAttachment" name="column"
								assignTo="{!sortExpression}"></apex:param>
						</apex:commandLink>
					</apex:facet>
				</apex:column>
				<apex:column>
					<apex:facet name="header">
						<apex:commandLink action="{!ViewData}"
							value="{!$ObjectType.Case.fields.CaseNumber.label}{!IF(sortExpression=='ParentId', 
							IF(sortDirection='ASC','▼','▲'),'')}">
							<apex:param value="ParentId" name="column"
								assignTo="{!sortExpression}"></apex:param>
						</apex:commandLink>
					</apex:facet>
					<apex:outputLink value="/{!e.ParentId}">{!e.Parent.CaseNumber}</apex:outputLink>

				</apex:column>
				<apex:column value="{!e.ToAddress}">
					<apex:facet name="header">
						<apex:commandLink action="{!ViewData}"
							value="{!$ObjectType.EmailMessage.fields.ToAddress.label}{!IF(sortExpression=='ToAddress', 
							IF(sortDirection='ASC','▼','▲'),'')}">
							<apex:param value="ToAddress" name="column"
								assignTo="{!sortExpression}"></apex:param>
						</apex:commandLink>
					</apex:facet>
				</apex:column>
			</apex:pageblocktable>
		</apex:form>
	</apex:pageblock>
</apex:page>

There are some other cool bits:

If there is no Case.Contact, the table will display the FromName, pulled from the email message.

An interesting point: You may notice that EmailStatus is presented in numerical form. For instance, Incoming Unread is 0, Incoming Read is 1, etc. The documentation, however, says, "Read only. The status of the email. For example, “New,” “Unread,” “Replied,” “Sent.”" So we're not sure of the exact mapping. 3 seems to be Sent, so 2 is probably Replied... but we're not sure.

Don't forget: EmailMessage has two lookups (foreign key): Case, and Activity. This Activity is the task created when Salesforce receives the email, and is - according to the documentation - assigned to the Case Owner. We're not sure what happens when the Case is owned by a Queue. Feel free to comment and share your experiences.

That's it! Enjoy.

X-Squared featured in Inc.com article on drop.io

March 19, 2009 · Filed Under X-Squared On Demand · Comment 

Those who follow us on Twitter know that drop.io is one of our favorite tools. We use it to collaborate with clients, to send files without taking up valuable inbox space (companies have been known to enforce miniscule Exchange inbox sizes), and even to share music playlists.
Howard Greenstein of Inc.com published a story today(“Stop, Collaborate, and Listen“) on drop.io and asked David to comment on how drop.io can help SMBs in service delivery.
It’s a good story that outlines the more basic features that drop.io offers. There is so much more available, both through the front-end and via drop.io’s RESTful API. In fact, David has been working on a Salesforce-drop.io integration that will allow inexpensive file storage and selective file-sharing with clients.
Please read Howard’s story and give drop.io a shot – you’ll enjoy it.

Zip to City & State Integration – Proof of Concept

March 11, 2009 · Filed Under salesforce.com · 2 Comments 

Zip to State & City - Proof of Concept

This is proof that our newest project, an integration to retrieve the State and City based on a postal code, works... sometimes. Obviously not ready for public consumption yet.

Next Page »