Pages

Monday, February 13, 2012

There's a Hole in My Bucket!

Ah, for many Salesforce users, Spring '12 was rolled out over the weekend. There are lots of new features that I've been chewing on in the pre-release sandbox and a few surprises.

One of the new features is the ability to create report "Buckets." Buckets let you create groups for a field's data without needing to create a formula field. I can't tell you how many formula fields I've created just to display the information I want to see on a report correctly. Of course - these bucket fields exist only on the report you're creating, so if you want to base workflow off of the field, it is off to the formula field editor for you.

Say you've got your list of clients, all equally important to you (of course), but you'd like to group them by the amount of assets they've brought to you. I'm not saying my Gold level clients are going to receive any special treatment over the Bronze level - I just want to know... really.

I'm going to classify my clients, using three tiers:
Gold: Anyone over 3,000,000
Silver: Between 1,000,000 and 3,000,000
Bronze: Below 1,000,000

1)  To create a new Bucket field, within a new/existing report, click on the "Add Bucket" link within the fields section.



2)  Next, double-check your "Source Column, give your Bucket field a name, and define your ranges:

3)  Click on the "OK" button and you'll see your newly created "Service Level" field within your report grid:


 
Unfortunately, like many initial rollouts of SF (I'm looking at you lookup field filters), it is lacking the ability to create and use filters.

What if I had a client that has been with me from day one.  Sounds like an instant Gold Service Level criterion to me.  It would be great to be able to include something like "Initial Contract Date < 1/1/2005" = Gold, else follow the ranged criteria.

For more information, check out this video, by my new favorite SF video narrator:  http://www.youtube.com/watch?v=QFYEtBtLHG4

Saturday, February 11, 2012

Don't Double Quote Me!

I came across an odd bug within a Visualforce page yesterday.  At least I think it is a bug - I can't explain it and it has been esclated to an upper tier of premier support.

Within an <apex:outputPanel />, I was using a "rendered" formula within the tag:

<apex:outputPanel rendered="{!If(AND(myObject__c.relatedObject__r.theSuspectField__c = 'The Picklist Value',myObject__c.myField2 > 0), true,false)}">


Ater I saved the page (and although the page would save), I started to receive warnings that I needed to close my tag. Thinking that was strange, I commented out everything between my and tags and received the same thing. Then I saw my rendered formula - that's strange, I must have used double quotes by accident. This is what I saw:

<apex:outputPanel rendered="{!If(AND(myObject__c.relatedObject__r.theSuspectField__c = "The Picklist Value",myObject__c.myField2 > 0), true,false)}">


I replaced the double quotes with single ticks again, hit save, and bam - they were converted to doubles again.

What gives?

Eventually I figured out that if I changed the order in which my fields were listed, the record would save as expected - no double quotes:

<apex:outputPanel rendered="{!If(AND(myObject__c.myField2 > 0,myObject__c.relatedObject__r.theSuspectField__c = 'The Picklist Value'), true,false)}">


I'm assuming that this it has something to do with myObject__c.relatedObject__r.theSuspectField__c being a picklist field, located on a related record.  Why it works in an alternate position in the formula, beats me, but is probably one of those strang quirks of Salesforce.

I'll update as I find out more.

Note:  I was using the inline VF editor, but saw the same issue when I attempted to resolve the issue within the Force.com/Eclipse IDE.

Tuesday, January 3, 2012

VF Templates: Formatted Merge Values (dates)

Just like in the previous post, another type of field that you might not receive your expected data format are date fields.
My record said: 

This is the VF code I was using:
{!relatedTo.myDateField__c}

The resulting text displayed in an email was:

That's not too pretty.
For date fields, you can extract individual pieces of the date, including the month, year, and date.  Once you pull those pieces out, you can separate them with a forward slash (“/”) and then it will look more like the date field you were hoping for.

I'm hoping for something that looks like "1/3/2012."

To retrieve the "1" for the month:

{!month(relatedto.Distribution_Form_Received_Date__c)}

To retrieve the "3" for the date:


{!day(relatedto.Distribution_Form_Received_Date__c)}


To retrieve the year "2012":

{!year(relatedto.Distribution_Form_Received_Date__c)}


Put them all together with those forward slashes:
{!month(relatedto.Distribution_Form_Received_Date__c)}/{!day(relatedto.Distribution_Form_Received_Date__c)}/{!year(relatedto.Distribution_Form_Received_Date__c)}


With that little bit of magic, your end result will be:

Monday, January 2, 2012

VF Templates: Formatted Merge Values (currency)

Once in a while, I'll run across something that I've forgotten all about.  Today, I was working with a Visualforce e-mail template that contained a currency field as one of the merge fields.

My record said:  $99.00
I was pulling it in with just this VF code:

{!relatedTo.myCurrencyField__c}

Just doing that resulted in the template displaying:
Not quite what I was looking for.  What I ended up doing was throwing my merge data into a pre-formatted apex:outputText field, using an apex:param:

<apex:outputText value="{0,number,$###,###,##0.00}">
    <apex:param value="{!relatedTo.myCurrencyField__c}" />
</apex:outputText>

This will format the dollar amount up to $999,999,999.99.  The 0's are there just incase your amount is not a full dollar ($0.89) or an amount that ends in 0 ($25.40).  Otherwise, those values are truncated ($.89 and $25.4 respectively).

Now the template looks like this:





To see more of what you can do, check out:  Java MessageFormat

Wednesday, December 21, 2011

Now you see it... Now you don't!

Part of a recent project requirement was to display a set of fields on a record from a related record.  One possibility could have been formula fields for each related field that needed to be displayed.  Sigh...

Another would have been to open a second window on a second monitor.  Nah... too easy

Of course you could create a Visualforce page that pulls these fields in and add it to the page layout.  That would work, but ah... the panel is part of the page, meaning you scroll - it moves.  Suddenly you're scrolling up and down and you might as well have had two windows open.  Forehead slap!

What do you do?

I'm a huge fan of the Force.com Quick Access Menu; one of the best time savers rolled out in a recent release.

It's just a small menu panel that floats at the side of the page.  The cool part about this is if you scroll, it moves with you.  Now, how in the world are you supposed to recreate that?

Fortunately for me, I didn't want it always displayed.  Because of that, I knew I had access to Custom Links and Custom buttons (read more).  They aren't just good for linking to pages, you can use them for launching onClick Javascripts too!
//Start of Custom Javascript Button Code
{!REQUIRESCRIPT("/soap/ajax/15.0/connection.js")}
 //Let's create a new DIV
var newdiv = document.createElement("div");
var result = sforce.connection.query("Select ID From ObjectID where ID = '{!ObjectID.ID}' AND other criteria if required);

//In my case, not every record would have a related record, so to avoid unnecessary errors we'll add make sure we have a result.  Granted, in your scenario, you may return more than one record.

if(result.size>0){
    var records = result.getArray("records");
    var newdiv = document.createElement('div');
    //I'm just going to build out my URL + parameters here
    var string1 = "apex/yourVFpage?core.apexpages.devmode.url=1&id=";
    var string2 = records[0].Id;
    var string3 = string1 + string2;
    newdiv.setAttribute('id', "thepanel");
    newdiv.style.width = "85%";
    newdiv.style.height = "255px";
    newdiv.style.position = "fixed";
    newdiv.style.left = "220px";
    newdiv.style.top = "0px";
    newdiv.style.background = "#FFFFFF";
    newdiv.style.border = "1px solid #000";
    newdiv.style.zIndex = "1000";
    newdiv.style.padding = "0px";
    //Use a regular old iframe to pull in your VF page
    newdiv.innerHTML = "<iframe src='"+string3+"' width='100%' height='240px' scrolling='false'/>";

//This DIV displays the up arrow graphic and provides the onClick functionality for hiding the panel
    var newdivUp = document.createElement('div');
    newdivUp.setAttribute('id', "UpPanel");
    newdivUp.style.width = "46px";
    newdivUp.style.height = "46px";
    newdivUp.style.position = "fixed";
    newdivUp.style.left = "174px";
    newdivUp.style.top = "0px";
    newdivUp.style.background = "transparent";
    newdivUp.style.zIndex = "1000";
    //Display the image and onClick hide this panel, display the down graphic panel, and hide the info panel
    newdivUp.innerHTML = "<img id='up' src='yourUpGraphic.png' onClick='document.getElementById(\"UpPanel\").style.display=\"none\";document.getElementById(\"DownPanel\").style.display=\"block\";document.getElementById(\"thepanel\").style.display=\"none\";document.getElementById(\"DownPanel\").style.display=\"block\";'/>";

//This DIV displays the down arrow
    var newdivDown = document.createElement('div');
    newdivDown.setAttribute('id', "DownPanel");
    newdivDown.style.width = "46px";
    newdivDown.style.height = "46px";
    newdivDown.style.position = "fixed";
    newdivDown.style.left = "174px";
    newdivDown.style.top = "0px";
    newdivDown.style.background = "transparent";
    newdivDown.style.zIndex = "1000";
    newdivDown.style.display = "none";
    //Displays the image and onClick hide this panel, display the up graphic and the info panel
    newdivDown.innerHTML = "<img src='youDownGraphic.png' id='down' onClick='document.getElementById(\"DownPanel\").style.display=\"none\";document.getElementById(\"UpPanel\").style.display=\"block\";document.getElementById(\"thepanel\").style.display=\"block\";' />";

//These three lines will add the DIVs created above to your page
    document.body.appendChild(newdiv);
    document.body.appendChild(newdivUp);
    document.body.appendChild(newdivDown);
}
//And just in case you don't return any records, display an error message
else if(result.size==0){
    alert('This record is not associated with an \'yourObject\' record. Please check the \'yourObject\' field and try again.');
}

Here's my end result in the "opened" state:

In the closed state:
And since you each DIV's position attribution to be "fixed", the panel, "x," or "+" will float at the top of your page for easy use when related data is needed for comparison purposes.

...or of course you could just open two browser windows.  Your choice.

Monday, October 24, 2011

Automated Phone Calls Via Workflow???

Sure, you've got four workflow actions that you can choose from when setting up your fancy new automated processes.
  • Tasks
  • Email Alerts
  • Field Updates
  • Outbound Messages
What if I said you could extend this to include:
  • Automated Calls
    • Pre-recorded messages
    • Robot voice messages (text-to-speech)
  • Text messages
Pretty sweet right?  Check out Call-em-All.  Let's get it out of the way now... I'm not affiliated with them and they were just a lucky Google search result that I found when searching for broadcast messaging solutions. Sign up for an account and then register for API authorization to get some test credits.

Through the GUI, you can setup call broadcasts; essentially the same as a mass e-mail, but with phone calls.  It's an easy service as all you do is upload your calls list (first name, last name, phone number) and record a message to be played.

They've got a great API that comes loaded with documentation, code samples, and a staging environment. 

You can leverage this to create some Apex that can be initiated by a button/link click, scheduled, or yes...even a trigger.

In the following sample, I will be initiating a call broadcast from my Opportunity by updating the Stage. Utilizing their text-to-speech feature, the call will dynamically include the Opportunity's name and stage. There will be two parts; the class and the trigger.

First, the class (excuse the body as it's lengthy):
public class Call_em {
@future (callout=true)
    public static void callMe(string a, string b){
        string Name = a;
        string stage = b;
        string username = 'username';
        string pin = 'pin';
        string broadcast_type = '1';
        string phone_number_source = '3';
        string broadcast_name = 'SAMPLE API Broadcast';
        string caller_id = 'caller_id';
        string PhoneNumbers = 'phonenumbers';
    string TTSText = 'This is a test call for the' + Name +' opportunity. The stage has been set to ' + stage;
        string proxy = 'http://staging-api.call-em-all.com/webservices/CEAAPI_v2.asmx?WSDL';

        HttpRequest req = new HttpRequest();
        req.setMethod('POST');
        req.setEndpoint('http://staging-api.call-em-all.com/webservices/CEAAPI_v2.asmx?WSDL');
        req.setHeader('Host', 'api.call-em-all.com');
        req.setHeader('Content-Type', 'text/xml; charset=utf-8');
        req.setHeader('SOAPAction', 'http://call-em-all.com/ExtCreateBroadcast');
        string bod = ''+username+''+pin+''+TTSText+''+TTSText+''+broadcast_name+''+broadcast_type+''+caller_ID+''+PhoneNumbers+''+phone_number_source+'';

        req.setbody(bod);

        Http http = new Http();
        HTTPResponse res = http.send(req);

        System.debug('These were the results: ' +res.getBody());
    }
}


And the trigger:
trigger Opportunity on Opportunity (before update){
    for(Opportunity o: trigger.new){
        if(yourcriteriahere!){
            Call_em.callMe(o.Name,o.StageName);
        }
    }
}


Explanations: I've kept the class as simple as possible for now. Ideally, you would probably not want to create a new broadcast every time the trigger was initiated. You would want to create your broadcast in advance and add the new number to it.
Note that the method is annotated with @future and callout=true. This is required as the trigger will make the callout asynchronously. The first part of the class is just setting up some variables. Looking at it, I don't think I used the proxy variable. My trigger will feed it two strings (yum); the Opportunity Name and the Opportunity Stage. I then create the new POST HTTPRequest, decalre the endpoint, and set up a few headers. Notice that my endpoint is set to their "staging" URL. I would recommend that this be set to a variable so that you could toggle between their staging and production environments relatively easily.

The trigger speaks for itself. You can use the "Call_em.callMe(o.Name,o.StageName)" line as an action for a button or even modify it to be used in a schedule Apex class.

Once you get this sample working, dig around in their API a little bit. You should be able to leverage existing broadcasts, modify lists, send text messages and so much more!

API Documentation: http://www.call-em-all.com/dev/docs.aspx

</end sales pitch> ...just kidding

Tuesday, October 18, 2011

Business Hours and Holidays!

The holidays are coming and do you know when your tasks are due?  There's an ongoing issue with Salesforce that I've inquired about throughout the years.  Although you can set business hours (even multiple profiles for hours) and holidays, these aren't taken into consideration when workflow actions are applied or due.

A few years ago, the answer to my inquiry was to research the IdeaExchange.  The idea already existed then and unfortunately is still just an idea today.

IdeaExchange Link:  http://success.salesforce.com/ideaView?c=09a30000000D9xtAAC&id=08730000000Bpq3AAC

What a shame!

Is there anything you can do?  Absolutely, but I hope you like Apex Triggers.

There is a BusinessHours Class that you can read about in the Apex Developer's Guide:  http://www.salesforce.com/us/developer/docs/apexcode/index.htm?businesshours

The first thing you want to do before you start playing around with that, though, is to set up your business hours by going to Setup --> Administration Setup --> Company Profile --> Business Hours

For my example, I'll have a default schedule of Monday through Friday, 8AM to 7PM.

I've also set up some holidays through Setup -->  Administration Setup --> Company Profile --> Holidays.  Don't forget to go back to your Business Hour schedule and add the holidays to it by clicking on the "Add/Remove" button.

Got that configured?  Now you're ready to give it a whirl.

Of course you'll want to work this into a trigger, but for demonstration purposes I've just created a test class and pulled in the system date/time (system.now()).

The VF Page:

<apex:page controller="test_business" >
    <apex:form >
        Current Date: {!oldDate}
            <br /><br />
        New (w/ GMT): {!newDateGMT}
    </apex:form>
</apex:page>

Explanation:  Just two simple getter methods from the test class.
  • oldDate is simply pulling in the current date/time using the system.now() method. 
  • newDateGMT is returning the adjusted date/time according to the line w/in that getter method

The Class:

public with sharing class test_business {
    public DateTime getoldDate(){
        DateTime oldDate = system.now();
    return oldDate;
    }

    public DateTime getnewDateGMT(){
        BusinessHours bh = [SELECT Id FROM BusinessHours WHERE IsDefault=true];
        Datetime oldDate = system.now();
        Datetime newDate = BusinessHours.addGMT(bh.id, oldDate, 3 * 60 * 60 * 1000L);
        return newDate;
    }
}

Class Explanation: 
  • getoldDate just retrieves the system.new() method as explained above.  This is the full GMT value, not adjusted for your time zone.
  • getnewDateGMT has two main parts
  1. The query is creating a BusinessHours object named bh after retrieving the one I created earlier and set as the default.
  2. Where I declare the newDate Datetime, I'm taking advantage of the addGMT method in the businesshours class.  I need to feed to it the ID of a BusinessHours object, a starting date, and my increment (in milliseconds).  Broken down, that looks like this:
    1. 3 - # of hours
    2. 60 - 60 seconds in a minute
    3. 60 - 60 minutes in an hour
    4. 1000L - 1000 milliseconds in second