Oh my god. It's full of code!

XML

Cloudspokes Scorecard REST API Entry

Well I just submitted my first attempt at an Apex RESP API. Overall I think it turned out pretty okay. I learned a lot, and I think I even created a cool new reusable class for converting XML into an updatable list of sObjects. Check out the video and leave any feedback!

Challenge Video


Apex Google Powered Navigation Class

Hey all,
Expanding on what I was talking about yesterday I have packaged my google maps navigation tool into an apex class. Using this class you can easily get directions to and from any address using the tried and tested google maps system. Simply provide the origin, destination, desired data return format, and a toggle indicating if you want to expand abbreviations in the directions (like rd to road, nw to northwest, dr to drive, etc).

You have several options for how you want the data to be returned. You can either return the raw xml data from google (kml as they call it) get a nice JSON data structure, a semi-colon separated list, or a semi-colon separated list that is url formated. Invoking the class is super easy.

String directions = getDirections.getDirections('address 1','address 2','dataReturnType', expandAbbreviations);

One thing that I dislike about the current incarnation is that the XML parsing is a bit slow/inefficient. I am not very strong on my XML-fu so currently it must iterate over every XML element to find the desired data. It would be faster to tell it more accuratly where to look, but I don’t really know how. So if you are a smarty pants developer and know how to make the search faster, please feel free to let me know. I’ll be happy to update it and credit you for the fix.

With this tool, you could easily use it as a controller for a visualforce page, and create your own RESTful style webservice, or use it to help build other neat services. It’s a nice developer resource I think.

Download it here or from the projects page.

NOTE: To use this class you will have to add http://maps.google.com to your remote sites in your org. Just go to setup->security->remote sites and add the url.


Coldfusion, Angel.com, Google Maps Directions, and You!

Okay, so even the title is a mouthful, this post is probably going to be insane you are thinking. Well… maybe, but it’s cool stuff. So picture this. You are using Angel.com as an IVR provider. So people call in and talk to a phone machine for data. Now say you want to give directions over the phone. Say you want those directions to be dynamic, and given to the user step by step. So for example you are hosting an event. People pre-register for this event, and have provided their address, which you have stored in a database. Bob Johnson calls in (he has registered and provided his address before) and wants directions from his house to your event center. You might think you’d need a live person to do this. Blasphemy! Have Bob authenticate so we can find his address in the database. Feed that address into an Angel.com variable (if there were a better way to enter addresses over the phone, you wouldn’t even need to pre-register, but because entering data in phones sucks we kind of need their address to already exist somewhere we can get it). Once that variable is in Angel.com, pass it, along with the destination to this tool via URL arguments. This tool will then give step by step directions that the IVR can read aloud back to Bob. He even has the ability to replay each direction, and navigate backward and forward through the steps.

Just copy and paste this and run host it on a ColdFusion server somewhere. It’s ready to be called with all configs just being passed in the URL at runtime.

<cfparam name="url.start" type="string" default="1405+Olive+Ln+N,+Plymouth,+Hennepin,+Minnesota+55447">
<cfparam name="url.end" type="string" default="1111+Cambridge+St.+Hopkins,+MN+55343+(White+Castle)">
<cfparam name="url.stepID" type="integer" default="1">

<!---- The id of the page that calls this webservice in angel.com ---->
<cfparam name="url.thisPage" type="string" default="1">

<!---- the id of the page to go to if this thing errors for some reason --->
<cfparam name="url.failPage" type="string" default="2">

<!---- the id of the page to go when we are all done giving directions ---->
<cfparam name="url.nextPage" type="string" default="3">

<!---- the id of the page to go to if we just can't find directions or a route ---->
<cfparam name="url.reEnterInfoPage" type="string" default="4">

<!---- the id of the page to go to if the person decides they want to talk to a person ---->
<cfparam name="url.transferToCC" type="string" default="5">

<!---- the id of the page to go to if the person wants to hang up---->
<cfparam name="url.disconnectPage" type="string" default="6">

<cfparam name="XMLData" type="string" default="">
<cfparam name="text" type="string" default="">

<!--- Format the directions for sending to google --->
<cfset url.start = replacenocase(url.start, " ", "+")>
<cfset url.end = replacenocase(url.end, " ", "+")>

<cfhttp url="http://maps.google.com/" result="KMLData">
    <cfhttpparam name="saddr" value="#url.start#" type="url">
    <cfhttpparam name="daddr" value="#url.end#" type="url">
    <cfhttpparam name="output" value="kml" type="url">
</cfhttp> 


<cfoutput>
    <cftry>
        <cfset XMLData = xmlParse(KMLData.FileContent)>
        <cfset totalNumberOfSteps = arraylen(XMLData.kml.Document.XmlChildren)-4>
        <cfset text = XMLData.kml.Document.XmlChildren[stepID+3].XmlChildren[1].XmlText>
        <cfset text = text&" . .">
        <!--- Some extra text formatting for reading over the IVR. You can easily add more abbreviations here if there
              are some I forgot --->
        <cfset text = replacenocase(text, " LN ", " Lane ")>
        <cfset text = replacenocase(text, " BLVD ", " Boulevard ")>
        <cfset text = replacenocase(text, " RD ", " Road ")>
        <cfset text = replacenocase(text, " ST ", " Street ")>
        <cfset text = replacenocase(text, " Ave ", " Avenue ")>
        <cfset text = replacenocase(text, " NW ", " North West ")>
        <cfset text = replacenocase(text, " NE ", " North East ")>
        <cfset text = replacenocase(text, " SE ", " South East ")>
        <cfset text = replacenocase(text, " SW ",  "South West")>            
        <cfset text = replacenocase(text, " N ", " North ")>
        <cfset text = replacenocase(text, " E ", " East ")>
        <cfset text = replacenocase(text, " W ", " West ")>
        <cfset text = replacenocase(text, " S ",  "South ")>

        
        <cfsavecontent variable="PromptMessage">
            #text#
            <cfif stepID LT totalNumberOfSteps>
                Press 1 for the next direction. Press 2 to repeat this direction. Press 3 to hear the previous direction.
                <cfelse>
                    You have reached your destination.
                    Press 1 to disconnect. Press 2 to repeat this direction. Press 3 to hear the previous direction.
            </cfif>    
        </cfsavecontent>
    
        <cfif stepID LT totalNumberOfSteps>
            <cfsavecontent variable="XMLLinks">
                <LINK dtmf="1" returnValue="#stepID+1#" destination="#url.thisPage#" />
                <LINK dtmf="2" returnValue="#stepID#" destination="#url.thisPage#" />
                <LINK dtmf="3" returnValue="#stepID-1#" destination="#url.thisPage#" />
            </cfsavecontent>
            
            <cfelse>
                <cfsavecontent variable="XMLLinks">
                    <LINK dtmf="1" returnValue="#url.nextPage#" destination="#url.thisPage#" />
                    <LINK dtmf="2" returnValue="#stepID#" destination="#url.thisPage#" />
                    <LINK dtmf="3" returnValue="#stepID-1#" destination="#url.thisPage#" />
                </cfsavecontent>            
        </cfif>
        
        
        <cfset counter = 1>
        <cfset VariablesObject[counter] = structnew()>
        <cfset VariablesObject[counter]["Name"] = "totalDirections">
        <cfset VariablesObject[counter]["Value"] = totalNumberOfSteps>

         

        <cfcatch type="any">
            <cfset promptMessage = "Sorry we couldn't find a route with the information supplied. Press 1 to try a different direction method. Press 2 to disconnect, or press 3 to be transferred to customer care.">
            <cfsavecontent variable="XMLLinks">
                <LINK dtmf="1" returnValue="#reEnterInfoPage#" destination="#failPage#" />
                <LINK dtmf="2" returnValue="#disconnectPage#" destination="#url.thisPage#" />
                <LINK dtmf="3" returnValue="#transferToCC#" destination="#url.thisPage#" />
            </cfsavecontent>        
            

            <cfset VariablesObject[1] = structnew()>
            <cfset VariablesObject[1]["Name"] = "ErrorType">
            <cfset VariablesObject[1]["Value"] = cfcatch.Type>        

            <cfset VariablesObject[2] = structnew()>
            <cfset VariablesObject[2]["Name"] = "ErrorMessage">
            <cfset VariablesObject[2]["Value"] = cfcatch.Message>

            <cfset VariablesObject[3] = structnew()>
            <cfset VariablesObject[3]["Name"] = "ErrorDetails">
            <cfset VariablesObject[3]["Value"] = cfcatch.Detail>                                    
        </cfcatch>
    </cftry>    
    
    <cfset ReturnObject = printQuestionReturnVariables('stepID',PromptMessage,XMLLinks,url.failPage,VariablesObject)>
#trim(ReturnObject)#
</cfoutput>


<cffunction name="printQuestionReturnVariables" access="remote" hint="Print Question Data For Angel IVR with returnable variables">
    <cfargument name="varName" default="none" type="string">
    <cfargument name="promptMessage" default="none" type="string">
    <cfargument name="linkMessage" default="none" type="string">
    <cfargument name="failPage" default="failPagePlaceholder" type="string">
    
    <!--- This is an array of structures, with keys "name" and "value" --->
    <!--- EX variables[1].Name = Gender --->
    <!--- EX variables[1].Value = Male --->
    <cfargument name="variablesToInclude" default="" type="any" required="no">
    
    <!--- create, scope and set the loop counter variable used below --->
    <cfset var i = 0>
    
    <cfoutput>
        <cfsavecontent variable="ReturnMessage">
            <ANGELXML>
                <QUESTION var="#ucase(varname)#">
                    <PLAY>
                        <PROMPT type="text">
                            #promptMessage#
                        </PROMPT>
                    </PLAY>
                    <RESPONSE>
                        <KEYWORD>
                            #linkMessage#
                        </KEYWORD>
                    </RESPONSE>
    
                    <ERROR_STRATEGY type="nomatch" reprompt="true">
                        <PROMPT type="text"> Sorry I did not get that. </PROMPT>
                        <PROMPT type="text"> I still did not get that. </PROMPT>
                        <PROMPT type="text"> Since I am having so much trouble; please hold while I transfer you to a customer representative who can better serve you. </PROMPT>
                        <GOTO destination="/25" />
                    </ERROR_STRATEGY>
                    <ERROR_STRATEGY type="noinput" reprompt="true">
                        <PROMPT type="text"> Sorry I did not get that. </PROMPT>
                        <PROMPT type="text"> I still did not get that. </PROMPT>
                        <PROMPT type="text"> Since I am having so much trouble; please hold while I transfer you to a customer representative who can better serve you. </PROMPT>
                        <GOTO destination="/25" />
                    </ERROR_STRATEGY>
                </QUESTION>        
                <cfif IsArray(arguments.variablesToInclude)>
                    <VARIABLES>
                        <cfloop from="1" to="#arraylen(arguments.variablesToInclude)#" index="i">
                            <cftry>
                                <cfif structkeyexists(arguments.variablesToInclude[i],'Name') and  structkeyexists(arguments.variablesToInclude[i],'value')>
                                    <VAR name="#ucase(arguments.variablesToInclude[i]['Name'])#" value="#arguments.variablesToInclude[i]['Value']#" />
                                </cfif>
                                <cfcatch type="any">
                                    <cfset ErrorData = structnew()>
                                    <cfset ErrorData.Error = cfcatch>
                                    <cfset ErrorData.Arguments = arguments>
                                    <cfset ErrorData.form = form>
                                    
                                    <!--- You might wanna email this data to yourself or something --->
                                 
                                </cfcatch>    
                            </cftry>                            
                        </cfloop>

                    </VARIABLES>
                </cfif>    
                
            </ANGELXML>
        </cfsavecontent>
    </cfoutput>
    
    <cfreturn ReturnMessage>
</cffunction>

So really what’s happening here is that the page gets called with some URL variables. Those variables are used to construct a google maps http request. That request actually prints out XML. We take the XML and clean it up a little bit and format it. Then print it off in a nice Angel XML package so it can be read by the system. The page just provides one step at a time, and uses a recursive style setup to just continually give directions until there are no more.

If you just want to use the step by step direction giving, you can of course easily remove all the Angel XML junk and just access the #return# variable and do whatever you like with it. This could easily be adapted to an online direction giver, or for Twilio, SMS, whatever. For now it is ColdFusion based, but I may try and convert it to an Apex class in the not too distant future. Depending on how the user is interacting with this, you could even remove the need to have the address pre stored. You could for example have a dedicated number where users just text their current address and you text them back step by step directions to your office or whatever. Really the sky is the limit here.

Here is a sample to see how it works. The demo has some extra stuff to make it more “person usable” instead of stripped down to be consumed by a computer.
Example of ColdFusion/Google Maps/Angel.com dynamic directions

Anyway, hope you guys think this is cool. It was a lot of fun to write!


Invoke Apex class via workflow rule

Since I started working with Apex, one thing I have wanted to be able to do is invoke a class via workflow rules instead of having to write triggers. The idea came to a head on a recent project in which users NEEDED to be able to configure when and how an Apex class was triggered without the need for coding. I wracked my brain for quite a while until I came up with a workable solution.

The easiest way to let a user control when something happens is by use of workflow rules. Workflow rules are basically ‘if then’ statements in which you define a condition, and an action. They are done using the GUI and have some fairly flexible and powerful features. This is where I decided to start my quest XD

Workflow rules give you three different actions you can take, each offered a different possibility of how I might use it invoke my Apex class, but in the end, only one survived. Those three actions being;

  • Send an outbound Message
  • Send an email alert
  • Perform a field update

My first thought was to use the outbound messaging functionality of workflow rules. Outbound messages encode user defined data in a SOAP (simple object access protocol) envelope in the HTTP request header, and pass it off to a target page (endpoint). The target page then will investigate the headers, extract the SOAP content (which is just XML) and do whatever it needs to do. So I thought, alright cool I’ll send my outbound message to a visualforce page which can then open up the contents of the message, find the information needed and use a web service called by the VF page to actually invoke the class. I began writing my code and everything was going well until I hit one crucial snag. Apex/Visualforce in it’s current incarnation Apex has NO way to access the HTTP request headers (you know, that part where the SOAP data is). So you can certainly send that data to the visualforce page with your workflow rule, but the page has no way to read the data. So if you just need to call an apex class but not pass any data to it, this method will work. It did not however work for because I needed to pass data to my class.

I wasn’t quite ready to give up on outbound messaging yet, because it just felt so right. It seemed like it had the answer I wanted so I kept thinking. It lead me to another idea. What if instead of calling the visual force page directly from the workflow rule, I call another page I control, on another server. Write a up a quick listener in ColdFusion that CAN get the SOAP data from the header, then just call a listener in my org that expects to receive data in the URL instead. So for example, my application sends text messages, so it needs a phone number and a message. The outbound message calls my ColdFusion ‘webservice’. The webservice takes the data it gets, parses the XML and extracts the phone number and message. It then calls a visualforce page hosted on a Salesforce site, and hands it that same data, except as URL arguments. Apex can get data from URL arguments, so this could work. The problem with this is that any data you want to include in your message MUST be on the object that triggered the rule. Outbound messages can’t spider relationships. Also, I wanted to package and possibly sell this application, so that means I’d have to somehow know which Salesforce site and page to call back with my URL arguments so now I need to let users register callback handlers and it just got really complicated. Too much so. I finally decided the outbound messaging idea was dead. If you have no need to package your app, and don’t mind your application not totally living in the cloud, this is also a workable solution.

So now I was stuck. How could I use my remaining 2 possibilities. Field updates pretty much seemed unworkable right from the get go. No way they would be flexible enough. So that left me with email alerts. I began thinking about the email to case functionality and the email services.

‘What if’ I thought ‘I were to create an email listener that would read the contents of emails and use those to call my trigger?’. The idea of creating an email service at first was a bit off putting, it seemed too clunky, but the more I thought the more it seemed workable. Also I had no other ideas and a looming deadline, so I went for it.

I decided the emails would contain a very simple XML structure would could be parsed, the data extracted and then used to call my apex class. The workflow rule simple calls the email template that has the XML structure and sends it to the email address that is listening. So I created the email service (really quite easy) and a simple Apex class to handle the data. After a bit of tweaking (there were some restrictions about who was allowed to actually email the service and it was rejecting my workflow user for a while without me knowing it.), it worked like a charm. Here is a the email service listenener that saved the day.

    global Messaging.InboundEmailResult handleInboundEmail(Messaging.InboundEmail email, Messaging.InboundEnvelope envelope) 
    {
        // declare the result variable
        Messaging.InboundEmailResult result = new Messaging.InboundEmailresult();
        
        // declare a string variable to catch our custom debugging messages
        String myErr;
        result.success = true;
        
        try
        {          
            // extract the email body : either the htmlBody or the plainTextBody
            String messageRecipPhone = '';
            String messageBody = '';  
            String messageContent = '';     

            messageBody = email.plainTextBody;
            messageBody = messageBody.trim();

            //Extract just the XML portion of the message
            messageBody = messageBody.substring(messageBody.indexOf('<?xml version="1.0"?>'),messageBody.indexOf('</emailData>')+12);

                        
            messageRecipPhone = readXMLelement(messageBody,'phoneNumber');
            messageRecipPhone = messageRecipPhone.replaceAll('\\D','');        
            
            messageContent = readXMLelement(messageBody,'message');
            
            if(messageContent.length() > 155)
            {
                messageContent = messageContent.substring(0,155);
            }
                                             
            Spartan_SMS_Message__c message = new Spartan_SMS_Message__c(Recipient_Phone_Number__c = messageRecipPhone, Message_Body__c=messageContent);
            insert message;
            
        }
        
        catch(exception e)
        {     
            Spartan_Error_Log__c log = new Spartan_Error_Log__c();           
            log.trace__c = e.getTypeName() + '\n' + e.getCause() + '\n' + e.getMessage();
            insert log;
        }

        return result;
    }

    public static String readXMLelement(String xml, String element)
    {
        String elementValue = 'NOT FOUND'; 
        
        try
        {
	        Xmlstreamreader reader = new Xmlstreamreader(xml);
	        while (reader.hasNext()) 
	        {
	            if (reader.getEventType() == XmlTag.START_ELEMENT && reader.getLocalName() == element)
	            {
	                System.debug('Found SID');
	                reader.next();
	                elementValue = getDecodedString(reader);
	            }         
	            reader.next();
	        }
	        return elementValue;
        }
        catch(exception e)
        {
			Spartan_Error_Log__c log = new Spartan_Error_Log__c();
            log.trace__c = e.getTypeName() + '\n' + e.getCause() + '\n' + e.getMessage() + '\n' + xml;
            insert log;
                    	
        	string err;
        	err = e.getTypeName() + '\n' + e.getCause() + '\n' + e.getMessage() + '\n' + xml;
        	return err;
        }
    }

You might look at that and say ‘but Dan, you didn’t actually invoke another Apex method at all in your code, you just created a record!’. Yes, very observant, but I have a trigger attached to that kind of record that does what I want. Also I could have easily invoked a class there if I wanted to. Also for your parsing pleasure I included a very simple XML parser that can get the value of any attribute when given valid XML and an attribute name. The function is also smart enough to just extract and parse the XML chunk of the email. You can put whatever crap you want before and after, and it won’t care, it will just happily do it’s job.

Also, since debugging email services and the like can be a pain, i created an object just for logging. If anything goes wrong, or I want an easily reviewable log of what happened, I can just create and insert a log record with whatever data I please. I highly recommend doing the same if you are going to be writing an app with any kind of complexity that produces output you might not otherwise get to see.

So there you have it, the best way I’ve found to allow users to choose when an apex class will be invoked and with what data. You simply create a workflow rule that sends an email containing XML data to an email service you have configured that will parse and use the data. If you have any questions feel free to ask, I’m happy to explain but I just didn’t want to drag this out any longer than I had to.


Salesforce outbound messages and visualforce

So I’ve been working on a project recently. A crazy one. One I hope to be able to package and sell on the app exchange.

What I am doing, is attempting to create a Salesforce application that will be packagable and sellable in the appexchange market. The challenge I am facing is allowing an end user to select when an apex class will be invoked, and pass their own data to it. I don’t know if a user will want to call a class when a contact is created, or a lead is updated, or an opportunity is deleted. The application is simply used for sending text (SMS) messages, based on criteria the user sets. The good news is that my application is pretty much done, save for one step. The bad news, that one step may be impossible, at least how I am trying to accomplish it.

My first thought on how to accomplish this was use a workflow rule (since workflow rules are easy enough for even regular end users to create) to send an outbound message to a visualforce page that comes with the app. The visualforce page would receive the SOAP data (which lives in the http request data), parse it out and send the data contained within to an apex class, which does the rest of the hard work of actually sending the message. The problem here is that, as far as I know, visualforce/apex have no way to access the data contained within the http request. Hence the SOAP data that is passed is useless, it is lost.

The workflow as I see it

The key thing is here, is that I simply need an easy way for users to select when the apex class will be called, and what data they will pass to it (it only needs the phone number and the message). I don’t really care how it happens as long as it is easy for the user to set up.

If this will not work, I only have two other ideas. One is to make the workflow rule send an email to a designated email address that has a bot monitoring the inbox, which checks the mailbox every few seconds, reads the emails, extracts the message data (the email would probably have XML in the body) then with that information call the apex method that sends the message. The other approach is to have the outbound messages go to a webservice that my company controls on premise, which COULD access the SOAP data in http request, parse it out, then call a visualforce page associated with the target account and pass the message and phone in the URL as arguments.

I have just emailed a Salesforce engineer that I met recently and maybe he’ll be able to come up with a way to doing what I want to accomplish. Otherwise, if you have any awesome ideas, feel free to leave them in the comments. I’ll give a free copy of the app to anyone who can solve this last problem.


Salesforce and Angel.com IVR

Salesforce is one of the largest (if not the largest) CRM provider. Angel.com is one of the largest interactive voice response unit providers. It’s likely at some point you are going to want to get a phone system to work with Salesforce, and thankfully it is fairly easy to do. Say for example you wanted to make a simple application that let a person check their Salesforce case status from the phone. It might work something like this…

1) Person calls in
2) IVR prompts person for their case number
3) IVR sends case number to Salesforce
4) Salesforce responds to the IVR with the case status and responsible agent.
5) IVR reads back the information to the person
6) End the call

(I know you’d want to add more features, but we’ll keep it simple for now). The part you are probably wondering about is how steps 3 and 4 work. We’ll lucky you I’m going to take you step by step through on how to do this.

Step 1: Build the Angel.com IVR site
First, your going to need a site which people can call into. Log in and create a new voice site. You’ll want to create 4 pages. First page should be some kind of welcome message that tells the person the service this site will offer. 2nd page will be a question page where they are prompted to enter their case number. 3rd page will be a transaction page that sends the case number to Salesforce, gets a response back and reads the results. The 4th page will be a message page that thanks them and disconnects.
Case Managment site in Angel

A the 2nd and 3rd page are the really important ones The 2nd page asks the person for their case number and stores it in variable. You can call that variable whatever you like, I just called it caseNumber. Salesforce case numbers are 8 digits, so made sure that a person has to enter 8 digits before moving on. Like so..

Get Case Info Page

The setup for page 20

The 3rd page is the transaction page responsible for sending the information to Salesforce. It will send the caseNumber with either the Get or Post method (use Get). I always include at least 2 other variables when I make transaction page calls, that I call nextPage and failPage. NextPage is where the XML should point the IVR to go if everything worked as expected. FailPage is where the IVR should go it the code encounters some kind of problem or unexpected result.This would normally bounce the person to customer support or something. For this example I am also including a retryPage, so if a case is not found the person can enter another case number if they want. Anyway, after that we tell it to expect the results as Angel XML (Angel’s XML data format, go figure).

Transaction Page

The page that sends info from Angel to SF

That is all we have to do on the Angel side for now, lets get the Salesforce part set up. This will consist of few steps.
1) Build the Salesforce site to house the required page.
2) Create the Class that actually gets and formats the data
3) Create the visual force page that will be called by the IVR and gets info from the above class
4) Write testing class and deploy.

So go ahead and log into your Salesforce org. Create a new Salesforce site. I called it IVR. This is my site for all kinds of IVR pages and classes. My setup looks like this.

Salesforce IVR Site

The setup for the Salesforce IVR site. Pretty simple.

Next we need to create a class that will take a case number, get the status and description and return it in some Angel XML. So fire eclipse (or the web based IDE if that floats your boat) and create a new class. I’m terrible at organizing code (never really been on a real dev team so never had anyone teach me) so I’m just going to call it getCaseInfoForAngel.

public class getCaseInfoForAngel 
//The default here is "public class with sharing" but that doesn't work. Remove the "with sharing"
{
    //Create a public variable to store the XML we will return to Angel. 
    public string AngelXML {get;set;}
    
    //Create the method that gets the info
    public void getInfoByCaseNum() 
    {
        //Take any variables passed in GET or POST and make them available in the params structure
        Map<string,string> params = ApexPages.currentPage().getParameters();
        
        //Variable to hold the text that will be read by the IVR to the user
        String promptMessage;
        
        //Variable that holds the mappings of the buttons a user can press to where that button will take them (ex press 1 to re-enter info)
        String xmlLinks = '';
        
        //a list of "structures" (a custom data type that contains name and value pairs) that can returned to Angel.
        //We won't need to pass any variables back to Angel for this example, but we'll just keep there here for future reference.
        List<Struct> angelContacts = new List<Struct>();

        List<Case> records = new List<Case>();
        
        try
        {        
            records = [Select Id, Status, Description
                                  from case
                                  where caseNumber = :params.get('caseNumber')];
               
               //If we found a case number
               if (!records.isEmpty()) 
            {    
                //Set the prompt message to read
                promptMessage = 'Your case status is '+records[0].Status+'. '+records[0].Description+'.';
                xmlLinks += '<LINK dtmf="1" returnValue="1" destination="'+params.get('nextPage')+'" />';
            }
            else
            {
                promptMessage = 'We were unable to locate any information for a case with that number. Press one to enter your case number again or two to disconnect.';
                xmlLinks += '<LINK dtmf="1" returnValue="1" destination="'+params.get('retryPage')+'" />';
                xmlLinks += '<LINK dtmf="2" returnValue="2" destination="'+params.get('nextPage')+'" />';
            }
                
        }
        catch(Exception e)
        {
                promptMessage = 'Sorry but we have encountered and error while getting your case information. Press 1 to enter your case number again or two to disconnect.';
                xmlLinks += '<LINK dtmf="1" returnValue="1" destination="'+params.get('retryPage')+'" />';
                xmlLinks += '<LINK dtmf="2" returnValue="2" destination="'+params.get('nextPage')+'" />';            
        }    
        
        //Create an Angel XML return object              
        createAngelXML A = new createAngelXML(); 
        
        //Set the AngelXML to the results of the call to the createAngelXML method.
        AngelXML = A.createXML('caseInfo',promptMessage,xmlLinks,params.get('failPage'),angelContacts);
    }
    public String getResult() 
    {
        //Return the results
        return AngelXML;    
    }
}

You’ll notice you are missing two things, the struct class, and the createAngelXML class. These are both supporting classes I wrote. Again, they are piss poor, not very flexible but they do the job and are easy to understand. I’m sure a real programmer can make these suck less, but they work for now.

Struct.cls

global class Struct {

    public String name;
    public String value;

    public Struct (String name, String value)
    {
         this.name = name;
         this.value = value;
    }
}

CreateAngelXML.cls

global class createAngelXML 
{
    public String createXML(String variableName, 
                          String textMessage, 
                          String linkMessage, 
                          String failPage,
                          List<Struct> AngelVariables)
    {

        String extraVariables = '';
        
        if(!AngelVariables.isEmpty())
        {
            extraVariables = '<VARIABLES>';
            for (Struct s: AngelVariables) 
            {
                extraVariables += '<VAR name="'+s.name+'" value="'+s.value+'" />';
            }
            extraVariables += '</VARIABLES>';
        }
        String responseText = '<ANGELXML>'+
                '<QUESTION var="'+variableName+'">'+
                    '<PLAY>'+
                        '<PROMPT type="text">'+
                            textMessage+
                        '</PROMPT>'+
                    '</PLAY>'+
                    '<RESPONSE>'+
                        '<KEYWORD>'+
                            linkMessage+
                        '</KEYWORD>'+
                    '</RESPONSE>'+
                    '<ERROR_STRATEGY type="nomatch" reprompt="true">'+
                        '<PROMPT type="text"> Sorry I did not get that. </PROMPT>'+
                        '<PROMPT type="text"> I still did not get that. </PROMPT>'+
                        '<PROMPT type="text"> Since I am having so much trouble; please hold while I transfer you to a customer representative who can better serve you. </PROMPT>'+
                        '<GOTO destination="'+failPage+'" />'+
                    '</ERROR_STRATEGY>'+
                    '<ERROR_STRATEGY type="noinput" reprompt="true">'+
                        '<PROMPT type="text"> Sorry I did not get that. </PROMPT>'+
                        '<PROMPT type="text"> I still did not get that. </PROMPT>'+
                        '<PROMPT type="text"> Since I am having so much trouble; please hold while I transfer you to a customer representative who can better serve you. </PROMPT>'+
                        '<GOTO destination="'+failPage+'" />'+
                    '</ERROR_STRATEGY>'+
                '</QUESTION>    '+  
                    extraVariables+
            '</ANGELXML>';
            
            return responseText;
    
    }
    
}

So with those, your getCaseInfoForAngel class should have everything it needs. It will take a case number, get the details and print them back in Angel’s expected format. Now we just need the visual force page to actually invoke that class. So create a new visualForce page. Call it whatever you want, I’m calling mine getCaseInfo It’s code will look like this.

<apex:page controller="getCaseInfoForAngel"  action="{!getInfoByCaseNum}"
contentType="application/x-JavaScript; charset=utf-8" showHeader="false" standardStylesheets="false" sidebar="false">
{!result}
</apex:page>

Alright, getting close, the hard stuff is done. Now we need to write the testing code, deploy it, adjust the site security, feed angel the new data about our site, and we see if it runs.

I’m not a big fan of testing classes, for me the best test is just to see if my stupid code runs, so I try go get by on as a little work as possible. For this class that means we need to create a case so we can get a case number. Which means we need a contact. Which means we need an account (You can see why test classes bother me, sigh). Anywho, here is some pre packaged testing code. It includes both a success case, and a failure case. Should get pretty close to 75% coverage I think.

       //create a new date object so we can easily insert it
        DateTime dT = System.now();
        Date myDate = date.newinstance(dT.year(), dT.month(), dT.day());

        //make an account so we can attach a contact to it
        Account TestAccount = new Account(name='My Test Account', Tax_ID__c='99-9999999');    
               
        //Now make a contact, so we can attach him as a respondent a few times
        date birthdate = date.parse('03/21/1988');
        Contact ContactGuy1 = new Contact(Firstname='Frank', 
                                            Lastname='Jones', 
                                            AccountID=TestAccount.ID, 
                                            of_PSA__c=0, 
                                            of_No_shows__c =0, 
                                            of_Cancellations__c =0, 
                                            of_Disqualified__c =0, 
                                            of_Participations__c =0, 
                                            MailingCity='coon rapids', 
                                            MailingCountry = 'us', 
                                            MailingState='MN', 
                                            MailingStreet='11021 bittersweet street', 
                                            MailingPostalCode='55433',
                                            phone = '7632344306',
                                            birthdate = birthdate);

        insert ContactGuy1;

        //Try to create the same case twice. Second one should error.
        Case case1 = new case(contact = ContactGuy1, status = 'Open', Origin = 'Web', subject='This is a test case. Eat face');
        insert case1;

        //***************** TEST getCaseInfo Class ********************************
        
        //Working test
        try
        {
            Case testCase = [select CaseNumber from case where id = :case1.id];
            PageReference getCaseInfo = Page.getCaseInfo;
            getCaseInfo.getParameters().put('caseNumber',testCase.CaseNumber);

            Test.setCurrentPageReference(getCaseInfo);
            getCaseInfoForAngel getInfoByCaseNumCtrl = new getCaseInfoForAngel ();
            getInfoByCaseNumCtrl.getInfoByCaseNum();
            myRunResult = getInfoByCaseNumCtrl.getResult(); 
        }
        catch(Exception e)
        {
            
        }
        
        //failure test    
        try
        {
            PageReference getCaseInfo = Page.getCaseInfo;
            getCaseInfo.getParameters().put('caseNumber','000000');

            Test.setCurrentPageReference(getCaseInfo);
            getCaseInfoForAngel getInfoByCaseNumCtrl = new getCaseInfoForAngel ();
            getInfoByCaseNumCtrl.getInfoByCaseNum();
            myRunResult = getInfoByCaseNumCtrl.getResult(); 
        }
        catch(Exception e)
        {
            
        }

So now go ahead and get your deployment ready. Select struct.cls, CreateAngelXML.cls, getCaseInfoForAngel.cls, your testing class, and your getCaseInfo visual force page and push them into prod (if you want, you can test in your sandbox, doesn’t matter really). Hopefully it should all deploy. If not post in the comments and I’ll try to help if I can.

Now we need to include all that stuff in our IVR Salesforce site. So go to your site configs, click public access settings. Modify the profile to have access to case info, and contact info just to be safe (since cases are related to contacts, not having contact info might cause issues, I dunno). Head to the enabled Apex classes section, and include struct.cls, createAngelXML.cls, and getCaseInfoForAngel.cls. Go to the enabled Apex Pages, and include getCaseInfo. If any of these things are not available in the lists, you might have to ajust their individual security settings. For example, my VisualForce page wasn’t available for me to select at first. So go to develop->pages->getCaseInfo->Security. Then I just enable it for all profiles to make life easy. I’m not sure which profile is that list is the one that does it, probably whatever profile your current user account has. EX

Setting VisualForce Page Permissions

f you are not able to select a needed page or class in Public Access Settings area, you may need to adjust the resources security like so

Okay, so now all classes and pages should be enabled for your site. You should now be able to access the getCaseInfo page. It would be something like
http://YourSalesForceSite.force.com/IVR/getCaseInfo
With any luck visitng that page should print out some AngelXML. If you get a Salesforce security message, you probably forgot a setting somewhere. It also shows that message if your code has an error, but my code never errors :P. The sample code I posted did work for me, so I’m pretty sure it does run. If you try it without giving it any case number it should print out the message “We were unable to locate any information for a case with that number”. You can give it a case number by adding a ?caseNumber=xxxxxxxx at the end to see what it does. It should now print our your case details.

So now with our functioning page, we need to hook it up to Angel. Go back to your transaction page in Angel. For the URL you can now enter the URL of your page. You can plug some test values in the boxes and see what happens. You should see it return some happy XML like so.

Happy XML Results!

Huzzah it worked!

And there you have it, you’ve built your first simple Salesforce and Angel.com integrated app. The beginnings of a customer service platform that lives totally in the cloud. Feel free to post code upgrades, questions, comments, whatever. I hope this helped some people out there, as it took me a long time to figure out, and almost as long to write about 😛


Simple XML Node Delete in Javascript

Hey everyone, I found the answer to another problem that had been bugging me for a while. When you want to delete a node from an XML file using javascript. Many examples only talk about how to remove an element when it is directly under the root node. Well that doesn’t work if your XML is at all deep. For those who just want the code it goes like this

//Find the node to kill
var y=XML.getElementsByTagName("NodeToDelete")[0];
//Find that nodes parent
var z=y.parentNode;
//delete the node from the parent.
z.removeChild(y);

Just replace NodeToDelete with whatever XML object you are trying to get rid of. A few things to know

– You only need to get the parent directly above the node you are trying to get rid of, you do not need to get the entire hierarchy.

– You can delete single attributes, or entire nodes using this.

– Remember if you delete nodes, your counters and stuff might get thrown off. If you delete node 3, what was 4 is now 4, what was 5 is now 4, etc. To help combat this issue, I wrote this quick little function

function FindNodeIndex(ID)
{
    //This function finds the index position of a node based on a given ID number. Normally an ID will be the same as the node index,
    //IE object0 will have an ID of 0 for the object, and it will also be first (0th) node in the XML. However, if objects are deleted
    //then the ID of the object and it's position in the XML may no longer match. If element 4 was deleted, element 5 still has an ID of
    //5, but it is now the 4th element in the XML. So with this function you can find the actual index of a node by passing in the ID.
    
    //set a default return number in case no node is found with the given id
    var nodePos = -1;
    
    //find how many nodes there are so we can loop over them.
    var XMLLength = XML.getElementsByTagName("id").length;
    
    //For every element, loop
    for (i=0;i<XMLLength;i++)
    {
        try
        {
            //read the value of the id attribute for the current node. If it is equal to the given id, we found the index position.
            //if not, keep searching.
            if(XML.getElementsByTagName("id")[i].childNodes[0].nodeValue == ID)
            {
                nodePos = i;  
                break;
            }
        }
        catch(e)
        {
            
        }
    } 
    
    //return the node position
    return nodePos;
}

So know say you want to know what the position of a node with the ID of 5 is, you can just call
FindNodeIndex(5)
and it will return the index position in the XML for you. Of course you’ll have to tweak my function above to look in your XML, and you may want to search on some attribute other than id (just change the referenced to id to whatever you want it to be). But it works for me, so I figured I’d share.

Again, this was all something that would be very easy for a more experienced programmer, but I spend what I felt to be way to much time trying to figure this out, so I though I might be able to save someone else out there some time. Hope it helps.