Syntax Highlighter JS

Wednesday, September 26, 2012

Salesforce App Exchange is sharing your information

The salesforce platform is most famous for its CRM / salespipe line and most people join salesforce to better manage their sales and/or customers.

Did you ever join salesforce to be sold to?  Probably not but due to some changes in the app exchange, if you watch a demo video you better be prepared to share your information with and be contacted by that vendor.

Some but not all demo's now require you to login.  I applaud those vendors that still have open demo videos.


If you  see the screen above and login to see the video, then your contact information will be sent to the vendor and most likely you will be contacted as follow up to a lead.

All of this will occur without you explicitly knowing that your information is being shared. If you are just browsing the exchange and not interested in buying, it simply wastes the time of the sales staff at this organization.

Worse yet, if you are going to be contacted for every demo you watch then you will most likely stop watching demos and/or investigating products as much.

I know this was intended to help boost revenues of both the vendor and Salesforce (they get a % from the app exchange) but I am afraid that it might have the opposite effect.

Salesforce needs to be more upfront about the information it is sharing with its partners and they also need to have an optional contact me check box so that only 'good' leads are forwarded to the sales staff.

Monday, September 17, 2012

USPS Address Standardization API Class

If you look around the app exchange for Salesforce, you can find many programs that will do address validation for you but almost all come at a price. Usually they charge per address transaction. Why pay this fee when you can do this for free?

In this post we are going to cover the main programming required to do this for free.  For now it is up to you to tie this to a GUI for use / testing but I hope to release a custom component to wrap all of this up for easier use.

UPDATE: Here is a link to the custom component.

On the USPS website you can find the E-Commerce APIs. One of those is the Address Information API. If you want to test the code below out, you will need to ahead and create a USPS API account and then request access to the address standardization API.  I recommend you do this upfront as it can sometimes take a couple of days to get access.

Note: this API is not meant for database cleaning but is instead meant for real time verification of addresses for items sent via USPS. You may also want to look at the Web Tools API Users Guide.

So lets start out with a class to hold all of our data:

public class USPSAddress { // in the USPS world, address 1 contains address 2 data (i.e. UNIT 101 or APT A) // So you will need to put the actual street address in address2 for proper parsing public string Address1 {get; set;} public string Address2 {get; set;} public string City {get; set;} public string State {get; set;} public string Zip5 {get; set;} public string Zip4 {get; set;} public boolean USPS_Returned_Error {get; set;} public string USPS_ERROR_CODE {get; set;} public string USPS_ERROR_DESC {get; set;} public string USPS_ERROR_SOURCE {get; set;} public USPSAddress() { Address1=''; Address2=''; City=''; State=''; Zip5=''; Zip4=''; USPS_Returned_Error = false; USPS_ERROR_CODE = ''; USPS_ERROR_DESC = ''; USPS_ERROR_SOURCE = ''; } // constructor public boolean HasData() { // this will return false if everything was defaulted. boolean ReturnValue = false; if (Address1 !='') { ReturnValue = true; } else if (Address1 !='') { ReturnValue = true; } else if (Address2 !='') { ReturnValue = true; } else if (City !='') { ReturnValue = true; } else if (State !='') { ReturnValue = true; } else if (Zip5 !='') { ReturnValue = true; } else if (Zip4 !='') { ReturnValue = true; } else if (USPS_Returned_Error !=false) { ReturnValue = true; } else if (USPS_ERROR_CODE !='') { ReturnValue = true; } else if (USPS_ERROR_DESC !='') { ReturnValue = true; } else if (USPS_ERROR_SOURCE !='') { ReturnValue = true; } return ReturnValue; } // HasData } // class USPSAddress

The code for the main class (including the test request #1 example) be sure to change your USER ID before using. Also, you will need to change the URL for initial testing and then back again for production access. public with sharing class USPS { private static final string USPS_UID = '<!-- INSERT YOUR ID HERE -->'; private static string BuildAddressQueryURLString(USPSAddress AddressToQuery) { // this function is coded to send only one address at a time // but it could be updated to support more (10 is the max ATM) by // iterating over addresses and incremending the address ID XML for each address // until you have one large URL as your query. // However, if you do modify this for mutiple addresses then you will need to // re-write the XML parse to handle that as well. String BaseURL = 'http://production.shippingapis.com/ShippingAPI.dll?API=Verify&XML='; String ReturnValue = '<AddressValidateRequest USERID="' + USPS_UID + '"><Address ID="0">'; ReturnValue += '<Address1>' + AddressToQuery.Address1 + '</Address1>'; ReturnValue += '<Address2>' + AddressToQuery.Address2 + '</Address2>'; ReturnValue += '<City>' + AddressToQuery.City + '</City>'; ReturnValue += '<State>' + AddressToQuery.State + '</State>'; ReturnValue += '<Zip5>' + AddressToQuery.Zip5 + '</Zip5>'; ReturnValue += '<Zip4>' + AddressToQuery.Zip4 + '</Zip4>'; ReturnValue += '</Address></AddressValidateRequest>'; ReturnValue = EncodingUtil.urlEncode(ReturnValue, 'UTF-8'); ReturnValue = BaseURL + ReturnValue; return ReturnValue; } // BuildAddressQueryURLString private static string GetStandardizedAddressFromUSPS(string USPSURLtoQuery) { string ReturnValue = ''; HttpRequest USPSRequest = new HttpRequest(); Http USPSHttp = new Http(); USPSRequest.setMethod('GET'); USPSRequest.setEndpoint(USPSURLtoQuery); HttpResponse USPSResponse = USPSHttp.send(USPSRequest); ReturnValue = USPSResponse.getBody(); system.debug('XML Response was: ' + ReturnValue); system.debug('-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-'); return ReturnValue; } // GetStandardizedAddressFromUSPS private static USPSAddress ParseUSPSResponseXML(String XMLToParse) { USPSAddress ReturnAddress = new USPSAddress(); // parse the response XMLStreamReader USPSXMLReader = new XMLStreamReader(XMLToParse); while (USPSXMLReader.hasNext()) { if (USPSXMLReader.getEventType() == XmlTag.START_ELEMENT) { if ('AddressValidateResponse' == USPSXMLReader.getLocalName()) { USPSXMLReader.next(); if ('Address' == USPSXMLReader.getLocalName()) { ReturnAddress = ParseUSPSAddressXML(USPSXMLReader); } // <Address ID="0"> tag } // <AddressValidateResponse> confirmation } // Starting tag USPSXMLReader.next(); } // loop thru UPS XML Reader if (ReturnAddress.HasData() == false) { // if parsing comes back totally blank then indicate an unknown / parsing error to the caller / requestor system.Debug('ReturnAddress.HasData() == false'); ReturnAddress.USPS_Returned_Error = true; ReturnAddress.USPS_ERROR_DESC = 'Unknown Error parsing XML Response'; ReturnAddress.USPS_ERROR_SOURCE = 'Salesforce XML Parsing'; ReturnAddress.USPS_ERROR_CODE = '-1'; } // ReturnAddress.HasData() == false return ReturnAddress; } // ParseAddressXML private static USPSAddress ParseUSPSAddressXML(XMLStreamReader USPSAddressXMLReader) { USPSAddress ReturnAddress = new USPSAddress(); while(USPSAddressXMLReader.hasNext()) { if (USPSAddressXMLReader.getEventType() == XmlTag.END_ELEMENT) { if ('Address' == USPSAddressXMLReader.getLocalName()) { // quit parsing when we hit the end of this record break; } // check for address ending tag } else if ('Error' == USPSAddressXMLReader.getLocalName()) { system.Debug('API Returned an error!'); ReturnAddress = ParseUSPSErrorXML(USPSAddressXMLReader); ReturnAddress.USPS_Returned_Error = true; } else if ('Address1' == USPSAddressXMLReader.getLocalName()) { USPSAddressXMLReader.next(); if (USPSAddressXMLReader.getEventType() == XmlTag.CHARACTERS) { system.Debug('Address1: ' + USPSAddressXMLReader.getText()); ReturnAddress.Address1 = USPSAddressXMLReader.getText(); } // check for data } else if ('Address2' == USPSAddressXMLReader.getLocalName()) { USPSAddressXMLReader.next(); if (USPSAddressXMLReader.getEventType() == XmlTag.CHARACTERS) { system.Debug('Address2: ' + USPSAddressXMLReader.getText()); ReturnAddress.Address2 = USPSAddressXMLReader.getText(); } // check for data } else if ('City' == USPSAddressXMLReader.getLocalName()) { USPSAddressXMLReader.next(); if (USPSAddressXMLReader.getEventType() == XmlTag.CHARACTERS) { system.Debug('City: ' + USPSAddressXMLReader.getText()); ReturnAddress.City = USPSAddressXMLReader.getText(); } // check for data } else if ('State' == USPSAddressXMLReader.getLocalName()) { USPSAddressXMLReader.next(); if (USPSAddressXMLReader.getEventType() == XmlTag.CHARACTERS) { system.Debug('State: ' + USPSAddressXMLReader.getText()); ReturnAddress.State = USPSAddressXMLReader.getText(); } // check for data } else if ('Zip5' == USPSAddressXMLReader.getLocalName()) { USPSAddressXMLReader.next(); if (USPSAddressXMLReader.getEventType() == XmlTag.CHARACTERS) { system.Debug('Zip5: ' + USPSAddressXMLReader.getText()); ReturnAddress.Zip5 = USPSAddressXMLReader.getText(); } // check for data } else if ('Zip4' == USPSAddressXMLReader.getLocalName()) { USPSAddressXMLReader.next(); if (USPSAddressXMLReader.getEventType() == XmlTag.CHARACTERS) { system.Debug('Zip4: ' + USPSAddressXMLReader.getText()); ReturnAddress.Zip4 = USPSAddressXMLReader.getText(); } // check for data } // check for end tags USPSAddressXMLReader.next(); } // loop thru XML reader return ReturnAddress; } // ParseUSPSAddressXML private static USPSAddress ParseUSPSErrorXML(XMLStreamReader USPSErrorXMLReader) { USPSAddress ReturnAddress = new USPSAddress(); while(USPSErrorXMLReader.hasNext()) { if (USPSErrorXMLReader.getEventType() == XmlTag.END_ELEMENT) { if ('Error' == USPSErrorXMLReader.getLocalName()) { // quit parsing when we hit the end of this record break; } } else if ('Number' == USPSErrorXMLReader.getLocalName()) { USPSErrorXMLReader.next(); if (USPSErrorXMLReader.getEventType() == XmlTag.CHARACTERS) { system.Debug('Error Number / Code: ' + USPSErrorXMLReader.getText()); ReturnAddress.USPS_ERROR_CODE = USPSErrorXMLReader.getText(); } // check for data } else if ('Source' == USPSErrorXMLReader.getLocalName()) { USPSErrorXMLReader.next(); if (USPSErrorXMLReader.getEventType() == XmlTag.CHARACTERS) { system.Debug('Error Source: ' + USPSErrorXMLReader.getText()); ReturnAddress.USPS_ERROR_SOURCE = USPSErrorXMLReader.getText(); } // check for data } else if ('Description' == USPSErrorXMLReader.getLocalName()) { USPSErrorXMLReader.next(); if (USPSErrorXMLReader.getEventType() == XmlTag.CHARACTERS) { system.Debug('Error Description: ' + USPSErrorXMLReader.getText()); ReturnAddress.USPS_ERROR_DESC = USPSErrorXMLReader.getText(); } // check for data } // check for ending element USPSErrorXMLReader.next(); } // loop thru XML reader return ReturnAddress; } // ParseUSPSErrorXML public static USPSAddress CheckAddress(USPSAddress Address2Check) { USPSAddress ResponseAddress = new USPSAddress(); // build the URL for the API call string USPSURL = BuildAddressQueryURLString(Address2Check); // call the API and pullback the XML as a string string XMLResponse = GetStandardizedAddressFromUSPS(USPSURL); // send the XML reponse to the parser ResponseAddress = ParseUSPSResponseXML(XMLResponse); return ResponseAddress; } // CheckAddress public static void RunUSPSTest1() { // create a new object to hold the data USPSAddress TestAddress = new USPSAddress(); // populate the data TestAddress.Address2='6406 Ivy Lane'; TestAddress.City='Greenbelt'; TestAddress.State='MD'; USPSAddress SearchResult = CheckAddress(TestAddress); } // Run USPS Test1 } // end USPS Class

Thursday, September 13, 2012

Loading Indicator on Visual Force pages with file upload

Due to issues beyond the control of Salesforce, you can't use a action status indicator on a form that uploads a file.

So how do you communicate to the end-user that an upload is going on (beyond the status bar in the browser)?   If you are uploading more than one file then the application can seem to hang or freeze to a user on a slow connection.

The first thing you will need is the Jquery java script library.    The second thing you need is the Jquery Block UI plug-in.

You can use a content delivery network hosted copy if you want but in my examples I will use them as a static resource in salesforce.

In this example, I have also created a static resource named Ajax_Loader.  This static resource is simply an animation that I would like to display while the transaction is being processed.

This static resource will be referenced in the blockme javascript function we are going to create.  This function will then be called by the onclick action of the command button.

Here is a sample visual force page:

<apex:page controller="testControllerExt" sidebar="false" showHeader="false" standardStylesheets="false" > <head> <apex:includeScript value="{!$Resource.jquery}"/> <apex:includeScript value="{!$Resource.jqueryblockUI}"/> <script type="text/javascript"> j$ = jQuery.noConflict(); function blockme() { j$.blockUI({message: '<img src="{!URLFOR($Resource.Ajax_Loader)}">'}); } </script> </head> <apex:outputpanel id="Main"> <apex:form > <apex:inputFile value="{!SampleFile.body}" filename="{!SampleFile.name}" id="SampleFile"/> <br/> <apex:commandbutton value="Submit" action="{!save}" onclick="blockme()" /> </apex:form> </apex:outputpanel> </apex:page>

With a matching controller, this page will have a loading message while the file uploads.  I have tested this in IE7 and Firefox 15.

Note:  This has been discussed before and I mention it here because the lack of an oncomplete attribute in the command button is what allows this to work with a file upload.

Retroactive undocumented changes in the force?

If you have programmed for a while, you might be familiar with the component object model.  This isn't new technology as it was introduced by MS way back in 1993 and is well documented.

One of the big parts of COM was the fact that interfaces were not versioned.  If any change was made to an interface then that became a totally new interface.  It is because of this design that any time you call an interface you should know exactly how it behaves because it never changes.

Salesforce provides version settings for apex and visual force pages for exactly this reason.  Here is a quote from their documentation:

"This ensures that as Apex, the API, and the components in managed packages evolve in subsequent released versions, a class or trigger is still bound to versions with specific, known behavior."

As I have previously reported,  Salesforce sometimes makes undocumented changes that affect apex classes and/or visual force pages - even if they are set to a previous version.

Today I noticed another interesting but undocumented change in the force platform.  Previously, if you tried to use an action component with a input file tag in your visual force page you would receive an error like this:

"apex:inputFile can not be used in conjunction with an action component, apex:commandButton or apex:commandLink that specifies a rerender or oncomplete attribute."

While working on another post, I tried to recreate this error in a page set to version 20.  To my surpise, I was able to save the page.   Of course, the action status didn't actually work with the file upload.
 

EDIT:  After wrapping the form in an output panel and setting the command button to rerender said output panel, I now receive the above error upon clicking the button on the page (as opposed to saving the page file).  So the error checking was moved from an obvious place to one where you can only find the issue by testing.


I made some changes to the page and changed the version to 25 and still no error and no functionality.   I checked the visual force developers guide to see if it still mentioned these limitations (as it did in the past) but they were no where to be found.  I know from previous experience this used to be a documented limitation.

Why did sales force remove the error checking and documentation for this limitation without actually making it work?   Also shouldn't such changes be limited to the new version?

This is important because it causes things to mysteriously break after updates.  If you are an independent software vendor or large corporation then you will most likely avoid these kinds of issues due to through testing of your system upon each release.

With the push to have more and more businesses move to the cloud, what about the smaller businesses? or the sole proprietor?  They end up spending money on consultants or spending time to figure out the cause and work around to problems that simply shouldn't have existed in the first place.  

With these kinds of undocumented changes going on, it can be very profitable for consultants go 'fix' code that was already working.

Note:
This case was a very limited example that had little to no actual impact but these kinds of changes go by unnoticed until something breaks.  I mean seriously, how often would you notice something behaving slightly different?  Most of the time you aren't going to notice this kind of thing until an actual error occurs.

Wednesday, September 12, 2012

Utility Class - Convert Attachment to EmailAttachment

Today's post is going to be a very obvious solution to most developers but is posted here for those learning apex code or those wanting a copy and paste solution.

The use case is that you have created a custom VF page on a site and are using it in a web-to-lead manner but taking it a step further by using a custom controller to send a confirmation email to the submitter.

The form in question just happens to have file attachments that you would like to send out in the confirmation email.   The issue is that the file coming in will be an attachment but you have to use an email attachment object type on your object going out.

I have written a rather simple method to do this:


static public Messaging.Emailfileattachment ConvertFileAttachmentToEmailAttachment(Attachment IncomingFile) { Messaging.Emailfileattachment ReturnEmailAttachment = new Messaging.Emailfileattachment(); if (IncomingFile != null) { if (IncomingFile.body != null) { Blob IncomingFileBodyBlob = IncomingFile.Body; ReturnEmailAttachment.setBody(IncomingFileBodyBlob); } ReturnEmailAttachment.setFileName(IncomingFile.Name); } // body != null return ReturnEmailAttachment; } // ConvertFileAttachmentToEmailAttachment

Right after you insert the attachment(s) you can do a call like this: RelatedBidsEmailAttachment = Util.ConvertFileAttachmentToEmailAttachment(RelatedBidsfile);
And then you can add the attachment(s) to a list: List<Messaging.EmailFileAttachment> FilesToAttachToEmail = new List<Messaging.EmailFileAttachment>(); FilesToAttachToEmail.add(RelatedBidsEmailAttachment);
And finally add them to your email: email.setFileAttachments(FilesToAttachToEmail);