Oh my god. It's full of code!

Latest

Communication Between Visualforce Pages and Standard Pages Via Iframes

So I had what I thought was going to be a quick easy project recently, an idea spawned from some idea spit-balling at the office. Basically we wanted to create a page where you could queue up records, and it would show you the edit screen for them one at a time. Hit save, next record comes up in edit mode, save it, next record comes up in edit mode. Sounds a lot like the inline list view editor I know, but this would give you the full edit screen, ability to modify rich text areas, etc. I dunno, some other people wanted it. I decided it sounded fun and easy enough to build so I decided to take a crack at it.

I had a few internal conditions I wanted to satisfy as well. I wanted it to be light weight, fast, and not require much if any configuration to work for any object in Salesforce. It should be flexible, easy and straight forward. I didn’t want some huge apex controller backing it, required custom buttons or anything weird like that. This was a simple project and it should have a simple solution. The approach I decided to go with was to create a visualforce page that received a list of Ids to edit, it would then request the record for each of those ideas via an iframe. The iframe avoids having to run any messy queries, page layouts are respected, all that good stuff. Using some URL hacks I could tell it to load the record straight in edit mode (if you append a /e on the end of a record url it loads straight into edit mode), and even set the return URL (where the browser is directed after the user saves/cancels), seemed easy enough. Have the frame load the first record, set the return url to being the edit mode of the next record, and boom just follow that process until you exhaust the list. I forgot one crucial thing, well not so much forgot as ignored and hoped I could figure a way around it. Cross domain iframe security.

Let’s back up one second. If you are not aware, when using iframes if the parent frame and the child frame are both in the same domain you can use javacript to transverse them, inspect the child, share variables in the global JS scope, all that good stuff. If they are not on the same domain about the only thing you can do is load the frame, and tell when it’s source changes/reloads. That’s it. You can’t insepct it, you can’t modify it, you have extremely minimal control. Salesforce hosts visualforce pages and standard object pages on different domains. Bummer. This means I could not modify the behavior of the save button, and I couldn’t know if the user saved, cancelled, or anything about what they were doing in the frame. This means that after the got passed the second record (the first I could load into the frame directly, and then by setting the returnURL I could get them to go to the second one) I could no longer control where the user was going to easily. Sure I could change the src on the iframe itself but the only time I could do that was during a load event, which quickly gets you stuck in a loop. I know it sounds like it should be easy, but take a whack at it, I was unable to figure out an elegant solution after a couple hours. Bottom line, this wasn’t going to be as easy as I thought.

So here I was kind of stuck, I could load the first record and even set it to saving and returning to the second record in edit mode, but that was about it. The iframe source doesn’t change on record saves, and there isn’t any way to modify the behavior of the save button to return to the next record URL. I did come up with a hacky version that by detecting every other page load I could change the source to the assumed next record. EX the first record loads and the user saves. Code detects 2nd load event (which is now the detail view of the first record after the save) and changes the source to the next record. Waits for another reload and forwards them again. It worked, but it was hacky, unsatisfying and required waiting for the detail version of the page to load before forwarding the user onto the next edit mode. There had to be a better way, but I’d need to somehow modify the contents of the iframe.

I can’t say exactly how I remembered, but suddenly a few pieces of information kind of hit me all at once.
1) Sidebar components can run custom javascript and run off the core salesforce domain, not the visualforce domain.
2) HTML5 spec gives us a postMessage API to frames from different domains to pass information to each other.
3) Javascript can run arbitrary code using eval.

Could I stitch all this information together to build some kind of javascript proxy to run code on the core domain on behalf of my visualforce page? Indeed I could, and now I’ll show you how. Gather round; Kenji’s got a cool new trick to show you.

First you are going to need to enable custom sidebar components on all pages. Head to:

setup-> user interface -> check: Show Custom Sidebar Components on All Pages

Now create a narrow HTML homepage component. Slap this code in there. Yeah I know eval is bad, you can of course replace this with specific function calls if you want but I wanted to play around. Besides SF is a mostly secure environment.

<div id="jsProxy" style="width:100%;height:100px"></div>
<script>
    function receiveMessage(event) {
		var result = new Object();
		result.success = true;
		result.callback = event.data.callback;
		result.command = event.data.command;
		
        try {
            document.getElementById("jsProxy").innerHTML = 'JS proxy received command: ' + event.data.command;
            result.data = eval(event.data.command);
         
        } catch (ex) {
			result.data = ex.message;
			document.getElementById("jsProxy").innerHTML = 'Error running command: ' + ex.message;
        }
		event.source.postMessage(result,event.origin);
    }
    window.addEventListener("message", receiveMessage, false);
</script>

What we just did is created an event listener that listens for any postmessages coming in. It hands them off to our receiveMessage function. That will set the content of our div so we can see what got passed in (mostly for debugging) and then attempts to run the received message data as code. This received code is now running in the context of the core salesforce page, not within visualforce. With this we can modify elements on the standard object interface.

So now you might be saying, okay great, you can pass code to the page to run, but still, how are you going to change the returnURL? It’s too late to try and pass that into the URL by the time the page loads right? You are right, but thankfully Salesforce usually just takes URL parameters and jams them into hidden fields and uses them for whatever from there. So it’s just a matter of changing the value of the hidden ‘retURL’ field. So I pass to my function a bit of code that locates that field and modifies it to be the url of the next record in edit mode. Something like this.

    //where to go next
    var nextRecord = multiEdit.objectIds[parseInt(indexPosition+1,10)];
    
    //create command to pass to listener to run
    message = 'document.getElementById("retURL").value = "/'+nextRecord+'/e"';
    
    //create container variable so we could specify a callback if we wanted to
    var setReturnUrl = new multiEdit.postMessageData(message,null);
    
    //send the command
    iframeMessageWindow.postMessage(setReturnUrl,'*'); 
                       
    multiEdit.receiveMessage = function(event)
    {
        if(event.data.callback != null)
        {
            window.multiEdit[event.data.callback](event);
        }
        
    }
    
    multiEdit.postMessageData = function(command, callback)
    {
         this.command = command;
         this.callback = callback;   
    }
   

    window.addEventListener("message",multiEdit.receiveMessage, false);

Boom, now the code for changing the field is passed via the postMessage API to the homepage component that has the ability to run said code and operate on the DOM within the iframe. Pretty slick eh? With this technique the border between visualforce pages and standard pages has pretty much been smashed. Sure it needs a bit of refining, but overall I think this could allow for some pretty cool stuff.

BTW I’ll probably be releasing the utility that I wrote using this approach fairly soon. Till next time!

Salesforce Orchestra CMS Controller Extensions

So I’ve been working with Orchestra CMS for Salesforce recently, and for those who end up having to use it, I have a few tips.

1) If you intend on using jQuery (a newer version than the one they include) include it, and put it in no conflict mode. Newer versions of jQuery will break the admin interface (mostly around trying to publish content) so you absolutely must put it in no conflict mode. This one took me a while to debug.

2) While not official supported, you can use controller extensions in your templates. However the class, and all contained methods MUST be global. If they are not, again you will break the admin interface. This was kind of obvious after the fact, but took me well over a week to stumble across how to fix it. The constructor for the extension takes a cms.CoreController object. As an alternative if you don’t want to mess with extensions what you can do is use the apex:include to include another page that has the controller set to whatever you want. the included page does not need to have the CMS controller as the primary controller, so you can do whatever you want there. I might actually recommend that approach as Orchestra’s official stance is that they do not support extensions, and even though I HAD it working, today I am noticing it act a little buggy (not able to add or save new content to a page).

3) Don’t be araid to use HTML component types in your pages (individual items derived from your page template) to call javascript functions stored in your template. In fact I found that you cannot call remoting functions from within an HTML component directly, but you can call a function which invokes a remoting function.

So if we combine the above techniques we’d have a controller that looks like this

global class DetailTemplateController
{
    global DetailTemplateController(cms.CoreController stdController) {

    }

    @remoteAction
    global static list<user> getUsers()
    {
        return [select id, name, title, FullPhotoUrl from user ];
    }
}

And your  template might then look something like this

<apex:page id="DetailOne" controller="cms.CoreController" standardStylesheets="false" showHeader="false" sidebar="false" extensions="DetailTemplateController" >
	<apex:composition template="{!page_template_reference}">
		<apex:define name="header"> 
			<link href="//ajax.aspnetcdn.com/ajax/jquery.ui/1.10.3/themes/smoothness/jquery-ui.min.css" rel='stylesheet' />

			<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
			<script> var jqNew = jQuery.noConflict();</script> 
			<script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min.js"></script> 

			<script>
        	        var website = new Object();
			jqNew( document ).ready(function() {
				console.log('jQuery loaded');
			});

			website.buildUserTable = function()
			{
				//remoting request
				Visualforce.remoting.Manager.invokeAction(
					'{!$RemoteAction.DetailTemplateController.getUsers}', 
					function(result, event){
						if (event.type === 'exception') 
						{
							console.log(event.message);
						} 
						else 
						{
							var cols = 0;

							var tbl = jqNew('#bioTable > tbody');
							var tr;
							for(var i = 0; i < result.length; i++)
							{
								if(cols == 0){tr = jqNew('<tr></tr>');}                              

								var td = jqNew('<td></td>');

								var img = jqNew('<img class="profilePhoto">');
								img.attr('src',result[i].FullPhotoUrl);
								img.attr('title',result[i].Title);
								img.attr('alt',result[i].Name);
								img.data("record", result[i]);
								img.attr('id',result[i].Id);

								td.append(img);

								tr.append(td);

								if(cols == 2 || i == result.length-1){
									tbl.append(tr);
									cols = -1;
								}
								cols++;

							}

						}
					})			
			}
			</script>
		</apex:define>
		<apex:define name="body">
			<div class="container" id="mainContainer">
				<div class="pageContent">
					<div id="header">
						<apex:include pageName="Header"/>
						<div id="pageTitle">
							<cms:Panel panelName="PageTitle" panelController="{!controller}" panelheight="50px" panelwidth="200px"/>
						</div>
					</div>
					<div id="pageBody">
						<p>
							<cms:Panel panelName="PageContentArea" panelController="{!controller}"  panelheight="200px" panelwidth="400px" />
						</p>
						<div class="clearfloat"></div>
					</div>

					<!-- end .content --> 
				</div>
			</div>
			<div id="footer_push"></div>
			<div id="footer">
				<apex:include pageName="Footer"/>
			</div>
		</apex:define>
	</apex:composition>
</apex:page>

Then in our page we can add an HTML content area and include

<table id="bioTable">
	<tbody></tbody>
</table>
<script>website.buildUserTable();</script>

So when that page loads it will draw that table and invoke the website.buildUserTable function. That function in turns calls the remoting method in our detailTemplateController extension that we created. The query runs, returns the user data, which is then used to create the rows of the table that are then appended to the #bioTable’s body. It’s a pretty slick approach that seems to work well for me. Your mileage may vary, but at least rest assured you can use your own version of javascript, and you can use controller extensions, which I wasn’t sure about when I started working it. Till next time.

Salesforce Live Agent Review & Customization

So you are building a new website hosted on force.com and your boss says

‘Oh and we gotta have chat. Everyone has chat these days, we need it.’

Agree, disagree, doesn’t matter. You are doing it because rent is coming due and you can’t tell him that that idea is as bad as his comb-over (all purely hypothetical of course). So you start thinking about writing your own chat app because it sounds like fun (some UI options, push notifications, some cool chances to use javascript remoting maybe?), then realize you don’t have time for fun because this thing is due in like a week. So you frantically google around a bit and realize,

‘Wait a minute, Salesforce has it’s own native chat app “live agent”. That could probably do most of my work for me!’

Only question is can you hack at it enough to make it do what you need? Does it have a pre-chat form? Does it have a post chat survey? Does it save logs? How about a queue system? The short answer is yes. I was actually blown away at how much time and energy they put into the live agent chat. It can do pretty much everything any reasonable person would ask of it, and a few unreasonable things as well.  As a developer though you just want to get it up and running as fast as possible so you can play with all the bells and whistles right? Manuals are readme’s are for suckers, let’s just throw it on a page somewhere as a POC to make the boss man happy. So how do you go about doing that, what does the process look like? In a nutshell, it’s going to go like this.

1) Get the Salesforce liveagent trial for your org.
2) Log into Salesforce and go to customize->live agent.
3) Create a deployment, and a button
4) Paste the scripts they give you onto your webpage
5) Feel slightly disappointed that it was too easy and you didn’t get to derive any satisfaction from solving a problem yourself.

OH! Before I forget, I felt really dumb when I first set this up because I put it all in place and couldn’t figure out how to actually let an agent login to the chat. I kept seeing something about a console view, but the console didn’t say anything about chat. It turns out you just have to create a console view (you know like you do for cases or whatever) and in the lower right corner there is a chat login in. It’s all within Salesforce, there is no other service to authenticate to or anything, which is pretty sweet.

So now you show your boss, and he’s like ‘Yeah that’s cool, but we’d like to know who we are talking to via a pre-chat form, and if they don’t exist in our system it would be cool to create a lead for them. Also if nobody is online we should just redirect them to a contact us form or something’. Ah-hah finally something fun! So you probably saw while creating your chat button there was a lookup field to a pre-chat form, but didn’t really have anything to populate that with. Nor do you have any idea how to build one. Well it turns out Salesforce actually has a pretty robust API centered around their chat, and a couple ways to pass information to the agent responding to the chat. I’m going to focus on the ‘simple’ form based approach since I haven’t used their javascript API they offer. So this pre-chat form can actually perform lookups, pass data, save information into the chat record itself, it’s pretty wild, but a little confusing. So first check out the pre-chat form sample Salesforce provides, it gives a good basic understanding of how it works.

Salesforce Pre-Chat Form Sample

You can see that you create fields, then some hidden fields take the values of the user entered fields and run queries or pass that info along to the console. You can populate fields on the chat record by creating ‘fields’ that look like this

<input type=”hidden” name=”liveagent.prechat.save:Email”  value=”Provided_Email__c” />

That says, take the value of the field called liveagent.prechat.Email from this form and save it into the Provided_Email__c field on the chat history object. Of course you could reference another hidden field in that name attribute and use code to set the value of that other hidden field allowing you to pass in basically whatever you like to the chat history. You can create custom fields and pass values to them, as well as passing values to the standard fields too.

But now we need to solve our issue of lookuping up the contact or lead based on email, and if one doesn’t exist creating one on the fly and returning that.  There are a few ways you could take this, but since I love javascript I decided to go with the javascript remoting approach. The user is going to enter their name and email, when they click submit instead of actually submitting the form a remoting function is going to run that runs our query and returns the result, or creates one. With that data we will populate the contactid or leadid field (depending what kind of record it is) and pass that along to the console by using javascript to submit the form once the function has run. Additionally using the ability to detect if there are agents online or not, we can change the behavior of the buttons (well actually we just show and hide different buttons) to send the user elsewhere if there is nobody online. It looks something like this.

<apex:page showHeader="false" controller="PreChatController">

<apex:variable var="deploymentId" value="572c0000000CaRW" />
<apex:variable var="orgId" value="00Dc0000001M6Ix" />
<apex:variable var="buttonId" value="573c0000000CaSe" />

<!-- This script takes the endpoint URL parameter passed from the deployment page and makes
it the action for the form -->
<script type='text/javascript' src='https://c.la7cs.salesforceliveagent.com/content/g/js/29.0/deployment.js'></script>

<script type='text/javascript'>
    liveagent.init('https://d.la7cs.salesforceliveagent.com/chat', '{!deploymentId}', '{!orgId}');
</script>

<script type="text/javascript">
    (function() 
    {
        function handlePageLoad()
        {
            var endpointMatcher = new RegExp("[\\?\\&]endpoint=([^&#]*)");
            document.getElementById('prechatForm').setAttribute('action',
            decodeURIComponent(endpointMatcher.exec(document.location.search)[1]));
        } 
        if (window.addEventListener) 
        {
            window.addEventListener('load', handlePageLoad, false);
        } 
        else 
        { 
            window.attachEvent('onload', handlePageLoad, false);
        }
    })();

    if (!window._laq) { window._laq = []; }

    window._laq.push(function()
    {
        liveagent.showWhenOnline('{!buttonId}', document.getElementById('prechat_submit'));
        liveagent.showWhenOffline('{!buttonId}', document.getElementById('liveagent_button_offline_{!buttonId}'));
    });

    function getLeadOrContact()
    {
        console.log('Getting lead or contact');
        var emailAddr = document.getElementById('email').value.trim();
        var fname = document.getElementById('name').value.trim();
        var phone = document.getElementById('phone').value.trim();

        try
        {
            Visualforce.remoting.Manager.invokeAction(
                '{!$RemoteAction.PreChatController.findLeadOrContactByEmail}', 
                fname,
                emailAddr,
                phone, 
                function(result, event)
                {
                    if (event.status) 
                    {
                        console.log(result);
                        if(result.Id.substring(0,3) === '003')
                        {
                            document.getElementById('contactid').value = result.Id;
                        }
                        else if(result.Id.substring(0,3) === '00Q')
                        {
                            document.getElementById('leadid').value = result.Id;
                        }
                        document.forms["prechatForm"].submit();

                        return true;
                    } 
                }, 
                {escape: false}
            );
        }
        catch(ex)
        {
            alert(ex.message);
            console.log(ex);
            return false;
        }
        return false;
    }   

</script>
<style>
body
{
    background-color:#f4f4f4;
}
#chatFormDiv
{
    width:200px;
    text-align:center;
    padding:5px;
}
#chatHeader
{
    color:#6d6d6d;
    font-size:18px;
    font-weight:bold;
}
label
{
    width:150px;
    font-weight:bold;
}
input[type=text], textarea
{
    width:200px;
    background: #f3f3f3; /* Old browsers */
    background: -moz-linear-gradient(top, #f3f3f3 0%, #ffffff 100%); /* FF3.6+ */
    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f3f3f3), color-stop(100%,#ffffff)); /* Chrome,Safari4+ */
    background: -webkit-linear-gradient(top, #f3f3f3 0%,#ffffff 100%); /* Chrome10+,Safari5.1+ */
    background: -o-linear-gradient(top, #f3f3f3 0%,#ffffff 100%); /* Opera 11.10+ */
    background: -ms-linear-gradient(top, #f3f3f3 0%,#ffffff 100%); /* IE10+ */
    background: linear-gradient(to bottom, #f3f3f3 0%,#ffffff 100%); /* W3C */
    filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f3f3f3', endColorstr='#ffffff',GradientType=0 ); /* IE6-9 */    
    border-color: #dedede;
    border-top-color: #d3d3d3;
}
textarea
{
    height:140px;
}
.chatStatusDiv
{
    display:none;
}
</style>

<div id="chatFormDiv">
    <img src="{!URLFOR($Resource.BeaconWebsite,'img/chatIconSmallGrey.png')}" /> <span id="chatHeader">Chat</span><br/>
    <hr />

    <form method='post' id='prechatForm' onsubmit="return false;" action="https://15af.la7cs.salesforceliveagent.com/content/s/chat?language=en_US#deployment_id={!deploymentId}&org_id={!orgId}&button_id={!buttonId}">

    <input type='text' name='liveagent.prechat.name' id='name' placeholder="Your Name" required="required"/><br />

    <input type='text' name='liveagent.prechat:Email' id='email' placeholder="Email Address" required="required" /><br />

    <input type='text' name='liveagent.prechat:Phone' id='phone' placeholder="Phone" required="required" /><br />

    <textarea name='liveagent.prechat:Body' id='body' placeholder="Message" required="required" ></textarea><br />

    <input name="liveagent.prechat.buttons" value="{!buttonId}" type="hidden" /><br />

    <!-- Creates an auto-query for a matching Contact record’s Email field based on the
    value of the liveagent.prechat:Email field -->
    <input type="hidden" name="liveagent.prechat.query:Email" value="Contact,Contact.Email" />

    <!--- populate fields ---->
    <input type="hidden" name="liveagent.prechat.query:Email" value="Lead,Lead.Email" />
    <input type="hidden" name="liveagent.prechat.save:Email"  value="Provided_Email__c" />
    <input type="hidden" name="liveagent.prechat.save:name"   value="Provided_Name__c" />
    <input type='hidden' name='liveagent.prechat:ContactId'   value='' id='contactid'/>  
    <input type="hidden" name="liveagent.prechat.save:ContactId" value="Contact" />

    <input type='hidden' name='liveagent.prechat:LeadId' id='leadid' />  
    <input type="hidden" name="liveagent.prechat.save:LeadId" value="Lead" />

    <!--- Button that shows up if someone is online --->    
    <img   src="{!URLFOR($Resource.BeaconWebsite,'img/chatButton.png')}" 
           alt="Submit Form" 
           onclick="getLeadOrContact()"
           id='prechat_submit'
           style="display: none; border: 0px none; cursor: pointer; float: left;"/>

    <!--- Button that shows up if nobody is online --->
    <img id="liveagent_button_offline_{!buttonId}" 
         style="display: none; border: 0px none; cursor: pointer; float: left;" 
         src="{!URLFOR($Resource.BeaconWebsite,'img/chatButton.png')}" 
         onclick="alert('nobody online, put your redirect here');"/>

    </form>
</div>
</apex:page>

And the Apex Controller looks like this

global class PreChatController
{

    @remoteAction
    global static sobject findLeadOrContactByEmail(string name, string email, string phone)
    {
        sObject returnObject; //the id that will store the contact or lead this chat is related to
        //first we should see if there is a contact with this email
        list<contact> contacts = [select accountid, name, id, email from contact where email = :email limit 1];
        if(!contacts.isEmpty())
        {
            return contacts[0];
        }

        //if there is no contact, then lets look for a lead instead. Yeah we could combind both queries into a single SOSL search, but the code for that doesn't
        //end up being much cleaner when you account for having to figure out what record to return when you have a list of lists.
        else
        {
            list<lead> leads = [select name, id, email from lead where email = :email limit 1];
            if(!leads.isEmpty())
            {
                return leads[0];
            }
            else
            {            
                lead thisLead = new lead();
                string[] nameParts = name.split(' ');

                thisLead.firstname = nameParts.size() > 1 ? nameParts[0] : ''; //if name parts contains more than one element that means we likely got a full name and the first part is the firstname. Otherwise nothing
                thisLead.lastname =  nameParts.size() > 1 ? nameParts[1] : nameParts[0]; //if name parts is greater than 1 then use the 2nd element as the lastname. Otherwise use the first element
                thisLead.phone = phone;
                thisLead.email = email;
                thisLead.company = name;
                thisLead.leadSource = 'Web Site';

                insert thisLead;
                return thisLead;
            }
        }
    }
}
Chatone

The user is asked to enter their information to connect with an agent.

chatTwo

When the agent is alerted that there is a person waiting to chat they automatically get a ‘screen pop’ informing them of the contact details

chatThree

The full contact or lead record is available to the agent making all their persons information instantly available. Much nicer than having to ask them a million questions.

Now when a user uses your chat form they will have to fill in their email, name and phone. Using that we can either locate them if they already exist, or create them on the fly and pass that information along to the agent. Pretty slick eh? One small bug that I dislike and currently do not know how to fix is that even though the contactId/leadId is passed to the console those fields are not populated visually. So your agent doesn’t see the lookups populated, even though they are. I think for now it’s just a training point to tell them. When the chat is saved and closed the transcript will be related to the contact/lead you just can’t see it on this screen. Weird I know.

Anyway I hope this helps getting you started and maybe even finished implementing Live Agent. It’s pretty cool and has a lot of features/power…. way more than I would have guessed. Best of all it makes you look like some kind of development wizard when you get go from request to implementation in like a few hours, so now you can get back to reddit… until the next request comes.

Protected: Saltybot – A decent into madness

This content is password protected. To view it please enter your password below:

Visualforce Force Download of PDF or Other Content

Hey everyone,

This next trick is one I’ve kind been keeping under my hat since it’s a nice polishing touch for some of my contest entries, but I figured I should probably share it with the world now (information must be free, etc). So we all know we can create Visualforce pages that render as PDF documents. It’s a pretty cool feature especially because business people love PDF files more than I love being a cynical ass (which is like… a lot). Though the one little annoyance is that normally when you create that PDF visualforce page the user is brought to it to view it where they then can download it. Many times they simply want to download it and attach it to an email or something, the viewing isn’t required and is generally just an extra few wasted seconds waiting for it to load so they can hit file->save as. I have found/built a nifty way to force download of the file using a combination of Apex and some tricky DOM manipulation. As an added bonus I’ll show you how to conditionally render the page as a PDF based on a URL param. Here we go!

The first thing we’ll need of course is our Visualforce page, we’ll keep it simple for this example. So here is our visualforce page

<apex:page controller="forceDownloadPDF" renderAs="{!renderAs}">
<h2>PDF Download example</h2>

<p>This is some content that could be displayed as a PDF or a regular web page depedning on the URL params. The valid URL params are as follows</p>
<table width="100%" cellpadding="5" cellspacing="5">
    <tr>
        <th>Name</th>
        <th>Type</th>
        <th>Default</th>
        <th>Required</th>
        <th>Description</th>
    </tr>
    <tr>
        <td>pdf</td>
        <td>String with a boolean value</td>
        <td>null/false</td>
        <td>false</td>
        <td>if passed in as a true the page will be rendered as a PDF. Otherwise displayed as HTML</td>
    </tr>
    <tr>
        <td>force_download</td>
        <td>String with a boolean value</td>
        <td>null/false</td>
        <td>false</td>
        <td>If true the user will be prompted to download the contents of the page. Suggested to be paired with pdf=true</td>
    </tr>
    <tr>
        <td>filename</td>
        <td>String (valid file name)</td>
        <td>'My PDF Report [todays date].pdf'</td>
        <td>false</td>
        <td>A name for the file. Only used if force_download=true</td>
    </tr>    
</table>

</apex:page>

And now our controller

public class forceDownloadPDF {

    public string renderAs{get;set;}

    public forceDownloadPDF()
    {

        //figure out if the user passed in the pdf url variable and if it is set to true.
        if(ApexPages.currentPage().getParameters().get('pdf') != null && ApexPages.currentPage().getParameters().get('pdf') == 'true') 
        {
            //if so, we are rendering this thing as a pdf. If there were other renderas options that were valid we could consider allowing the user to pass
            //in the actual renderAs type in the url, but as it stands the only options are pdf and null so no reason to allow the user to pass that in directly.
            renderAs = 'pdf';

            //figure out if we are forcing download or not.
            if(ApexPages.currentPage().getParameters().get('force_download') != null && ApexPages.currentPage().getParameters().get('force_download') == 'true') 
            {
                //setup a default file name
                string fileName = 'My PDF Report '+date.today()+'.pdf';

                //we can even get more created and allow the user to pass in a filename via the URL so it can be customized further
                if(apexPages.currentPage().getParameters().get('filename') != null)
                {
                    fileName = apexPages.currentPage().getParameters().get('filename') +'.pdf';
                }
                //here is were the magic happens. We have to set the content disposition as attachment.
                Apexpages.currentPage().getHeaders().put('content-disposition', 'attachemnt; filename='+fileName);
            }               
        }        
    }
}

As noted in the comments the real secret here is setting the content disposition use the Apex getHeaders method. Now you are saying,

‘But Kenji if I call that page from a link it still opens in a  new window it just forces the user to download the file. That’s not much better!’

Oh ye of little faith, of course I got you covered. You think I’d leave you with a half done solution like that? Hell no. Lets take this mutha to the next level. Here is what we are going to do. Using a custom button with onClick javascript we are going to create an iframe with the source set as that visualofrce page (with the force_download=true param) and inject it into the DOM. When the frame loads (which will have 0 width, and height so it’s not visible) that code still runs prompting the user to download the file. They are non the wiser that a frame got injected, all they see is a happy little download dialog prompt. So go create a custom button on an object that you want to prompt the user to download your file from. Make it a detail page button (you could do a list button to, but that’s a topic for another day). Make it onClick javascript. Then slap this code in there.

ifrm = document.createElement("IFRAME"); 
ifrm.setAttribute("src", "/apex/yourPage?pdf=true&force_download=true&filename=My Happy File"); 
ifrm.style.width = 0+"px"; 
ifrm.style.height = 0+"px"; 
document.body.appendChild(ifrm);

Of course replace the ‘yourPage’ with the name of your visualforce page. The filename of course can be changed to be details from the record, or whatever you like. Now when the user clicks that button the javascript creates an invisible iframe and injects it into the DOM. Once it loads the user is prompted to download the file. Pretty slick eh?

Hope you dig it. Catch ya next time.

Building a Better WordCloud

Hey all,

I know it’s been a while since my last post. Fun projects with cool end results have been rare and I’m not the type to post stuff just to fill space so I’ve kinda just been chillin recently. Though that changed when I was asked to take another whack at putting together a word cloud app, this time for Salesforce. You may or may not remember that I created a small word cloud app a while ago that didn’t have much anything to do with Salesforce and used a PHP back end that took free text replies from lime survey and created a real time word cloud. This time the task is a little different. I was asked to create a word cloud application that would take data from multiple records, and multiple fields, aggregate it all together and create a cloud from the resulting text blob. Real time updating was not requested, so I had some more flexibility in architecture. I also wanted to take this change to upgrade the actual display a bit since I wasn’t very pleased with my last attempt (it was functional, but a little clunky. Manual CSS rules specified formatting, etc).

Since I knew there must be a good word cloud generator out there I did a bit of searching and decided on the jQuery plugin ‘awesomeCloud’ which makes very stylish clouds without being overly complex. It’s licensing is pretty open (GPL v3) so it looked like a good fit. You can check out some of the sample word clouds in generates on the awesomeCloud demo page. It’s got some nice options for themeing, cloud shape, normalization and more.

So invocation is the word cloud is pretty easy, you just need to create a div with spans in it that have a data-weight attribute that specifies the frequency of that word. You then just call the plugin with the options you want on the container div. Like so

Example of creating a wordcloud (taken from the github page of awesomeCloud)

Example of creating a wordcloud (taken from the github page of awesomeCloud)

Easy enough? But now comes the tricky part, how do we get the data from the objects and fields count the word frequencies and then insert them into the DOM? That is where we come in. I decided that since this is going to be a pretty light weight application with most likely fairly small queries we could get away with using the ajax toolkit thus avoiding the extra complexities of Apex controllers. As usual I’m defaulting to using javascript where I can. You could of course modify the approach below to use Apex if it makes you happier. Whatever works for you. Anyway, lets get on with it.

I decided that since this code may be used in different places to do different things (maybe some inline pages, a stand alone VF page, a dashboard component maybe?), it made sense to make it into a component. Makes it easier to pass in configuration parameters as well, which I figured people would want to do. I’d recommend doing the same. First up lets go ahead of define the parameters we are going to allow to be passed into the component. Most of these are for configuring the word cloud itself, but there are a few others.

    <apex:attribute name="objectType" description="type of records to get data from" type="String" required="true"/>
    <apex:attribute name="records" description="records to get data from" type="String" required="false"/>
    <apex:attribute name="fields" description="fields on records to aggregate data from" type="String" required="true" default="Name"/> 

    <apex:attribute name="lowerbound" description="words below this frequency will not be dipslayed" type="String" required="false" default="0"/> 
    <apex:attribute name="skipwords" description="words not to display regardless of frequency" type="String" required="false" default="and,the,to,a,of,for,as,i,with,it,is,on,that,this,can,in,be,has,if"/> 

    <apex:attribute name="grid" description="word spacing; smaller is more tightly packed but takes longer" type="integer" required="false" default="8"/>    
    <apex:attribute name="factor" description="font resizing factor; default 0 means automatically fill the container" type="integer" required="false" default="0"/>
    <apex:attribute name="normalize" description="reduces outlier weights for a more attractive output" type="boolean" required="false" default="false"/> 

    <apex:attribute name="font" description=" font family, identical to CSS font-family attribute" type="string" required="false" default="Futura, Helvetica, sans-serif"/> 
    <apex:attribute name="shape" description="one of 'circle', 'square', 'diamond', 'triangle', 'triangle-forward', 'x', 'pentagon' or 'star'" type="string" required="false" default="circle"/> 

    <apex:attribute name="backgroundColor" description="background color" type="string" required="false" default="transparent"/>
    <apex:attribute name="colorTheme" description="dark or light" type="string" required="false" default="light"/>

    <apex:attribute name="width" description="how wide should the cloud be (css values such as a pixel or inch amount)?" type="string" required="false" default="600px"/>  
    <apex:attribute name="height" description="how tal; should the cloud be (css values such as a pixel or inch amount)?" type="string" required="false" default="400px"/>  
    <apex:attribute name="autoRefresh" description="should the wordcloud automatically refresh every so often?" type="boolean" required="false" default="false"/>

    <apex:attribute name="refreshInterval" description="how often shold the cloud refresh? In seconds" type="integer" required="false" default="5"/>

Of course you’ll need to include the required javascript libraries.

<script src="//code.jquery.com/jquery-latest.js"></script>
<script src="{!$Resource.jQueryWordCloud}"/>
<apex:includeScript value="/soap/ajax/15.0/connection.js"/>
<apex:includeScript value="/soap/ajax/15.0/apex.js"/>

Now we’ll need to start putting together the javascript functions to get the data and do the word frequnecy analysis. First up, lets create a javascript method that can build a dynamic query to get all the required data. Using the ajax toolkit it looks something like this

function getData(objectType,recordIds, fields)
{
    //build the basic SOQL query string
    var queryString = "Select "+fields+" from "+objectType;

    //if we are limiting the query by searching for specific object ids then add that condition
    if(recordIds.length > 0)
    {
        var whereStatment = "('" + recordIds.split("','") + "')";
        queryString += ' where id in ' + whereStatment;
    }
    //make sure to put a limit on there to stop big query errors.
    queryString += ' limit 2000';

    //run the query
    result = sforce.connection.query(queryString);

    //get the results
    records = result.getArray("records");

    //lets aggregate all the fetched data into one big string
    var wordArray = [];

    //we want to build an array that contains all the words in any of the requested fields. So we will
    //put all the desired fields into an array, iterate over all the records, then over all the fields
    //and stash all the data in another array.

    //fields is a comma separated string. Lets split it into an array so we can iterate over them easily 
    var fieldsArray = fields.split(',');

    //loop over all the records
    for (var i=0; i< records.length; i++)
    {
        var record = records[i];
        //loop over all the fields that we care about
        for(var j=0; j<fieldsArray.length; j++)
        {
            //get the value of this field from this record and add it to the array
            wordArray.push(record[fieldsArray[j]]);
        }
    }  
    //we will now pass in all the words from all the records fields into the getWordFrequnecy function. By calling join
    //on the array with a space character we get one big ass text chunk with all the words from all the records.

    var frequencyResult = getWordFrequency(wordArray.join(' '));

    //pass our frequnecy result data into the load cloud function
    loadCloud(frequencyResult); 
}

With that we can pass in an sObject type, an optional list of record ids (comma separated) and a list of fields to get data from on those records (also comma separated). Now we need to build the getWordFrequency function. That’s going to take a chunk of text and return an array of objects that contain a tag property, and a freq property. That data then gets fed into awesomeCloud to generate the cloud.

            //takes a string and counts the freqnecy of each word in it. Returns array of objects with tag and freq properties.
            //words should be space separated
            function getWordFrequency(wordString){

                //convert string to lower case, trims spaces, cleans up some special chars and splits it into an array using spaces and delimiter.
                var sWords = wordString.toLowerCase().trim().replace(/[,;.]/g,'').split(/[\s\/]+/g).sort();
                var iWordsCount = sWords.length; // count w/ duplicates

                // array of words to ignore
                var ignore = '{!skipwords}'.split(',');
                ignore = (function(){
                    var o = {}; // object prop checking > in array checking
                    var iCount = ignore.length;
                    for (var i=0;i<iCount;i++){
                        o[ignore[i]] = true;
                    }
                    return o;
                }());

                var counts = {}; // object for math
                for (var i=0; i<iWordsCount; i++) {
                    var sWord = sWords[i];
                    if (!ignore[sWord]) {
                        counts[sWord] = counts[sWord] || 0;
                        counts[sWord]++;
                    }
                }

                //get the lower bound as an integer. Lower bound controls the minimum frequnecy a word/tag can have for it to appear in the cloud
                var lowerBound = parseInt({!lowerbound},10);
                var arr = []; // an array of objects to return
                for (sWord in counts) {
                    if(counts[sWord] > lowerBound)
                    {
                        arr.push({
                            tag: sWord,
                            freq: counts[sWord]
                        });
                    }
                }

                /* Sorting code, not really required for this purpose but kept in case it is decided that we want it for some reason.
                // sort array by descending frequency | http://stackoverflow.com/a/8837505
                return arr.sort(function(a,b){
                    return (a.freq > b.freq) ? -1 : ((a.freq < b.freq) ? 1 : 0);
                });
                */

                return arr;

            }

Alright, so now we have all the data from all the records analyzed and the frequency of each word is known. Words we have decided to skip have been left out and those that don’t make the cut from the lower bound are also excluded hopefully leaving us only with actually interesting words. Now we have to pass in the that data to awesomeCloud along with our settings.

function loadCloud(data) 
{ 
    //wordcloud settings
    var settings = {
            size : {
                grid : {!grid},
                factor : {!factor},
                normalize: {!normalize}
            },
            color : {
                background: "{!backgroundColor}"
            },
            options : {
                color : "random-{!colorTheme}",
                rotationRatio : 0.5
            },
            font : "{!font}",
            shape : "{!shape}"
    } 

    //create array for tag spans
    var wordStringArray = [];

    //evaluate each array element, create a span with the data from the object
    $.each(data, function(i, val)
    {
        wordStringArray.push('<span data-weight="'+val.freq+'">'+val.tag+'</span>');
    });

    //join all the array elements into a string. I've heard to push stuff into array and join then push to the DOM is faster than
    //than modifying a string (since strings are unmutable) or modifying the DOM a ton, which makes sense.
    $( "#wordcloud" ).html(wordStringArray.join(''));

    //setup our word cloud
    $( "#wordcloud" ).awesomeCloud( settings );
}

Alright, so now we just need to invoke all this these crazy functions. We’ll do that with a functional call in the document onready to make sure everything is loaded before we start going all crazy modifying the DOM and such.

//login to salesforce API so we can run query
sforce.connection.sessionId = '{!$Api.Session_ID}';
$(document).ready(function() {
    //make an immediate call to getData with the info we need from the config params.
    getData('{!objectType}', '{!records}', '{!fields}');

    //if we are doing an auto refresh, rig that up now using the setInterval method
    if({!autoRefresh})
    {
        setInterval ( "getData('{!objectType}', '{!records}', '{!fields}')", parseInt({!refreshInterval},10) * 1000 );
    }
});

Finally we just need to create our HTML container for the wordcloud and setup the CSS style.

    <style>

    .wordcloud {
        /*border: 1px solid #036;*/
        height: {!height};
        margin: 0.5in auto;
        padding: 0;
        page-break-after: always;
        page-break-inside: avoid;
        width: {!width};

    }

    </style>
    <div id="container" title="wordcloud with the content of fields {!fields} for objects {!records}"> 

        <div id="wordcloud" class="wordcloud" ></div>
    </div>

Whew, alright so that’s it for our component. Now we just need a visualforce page to invoke it. That part is easy. Although our word cloud is capable of aggreating data from multiple objects, for this demo we’ll keep it simple. We will create a little inline visualforce page that can live on the account object. It will create a wordcloud from the description, name, type and ownership fields. To do that, create a visualforce page like this

<apex:page standardController="account">
    <!--- 
    WordCloud comes as a component that can be invoked from any visualforce page. You must pass it the object type to build the cloud for. The rest is optional.
    objectType: the type of sObject to get data from to power the word cloud
    fields: the records on the objects who's content will be used to create the cloud. Must be comma separated
    records: a list of ids which to query for. If none is provided all records of the objectType are queried
    skipwords: words that will not be included in the word cloud no matter how many times they appear. 
    lowerbound: the minimum number of times a word must appear in the text before it is displayed in the cloud.
    grid: word spacing; smaller is more tightly packed but takes longer
    factor: font resizing factor; default "0" means automatically fill the container
    normalize: reduces outlier weights for a more attractive output
    shape: shape of the cloud. Must be one of "circle", "square", "diamond", "triangle", "triangle-forward", "x", "pentagon" or "star"
    font: font family, identical to CSS font-family attribute
    width: width of the cloud. Can be a percent or pixel/inch amount.
    height: height of the cloud. Must be a pixel or inch amount.
    backgroundColor: a hexidecimal (#000000) or string color value. Use 'transparent' for no background
    colorTheme: theme for word colors. Use 'dark' or 'light'
    autoRefresh: automatically refresh the cloud after a specified interval?
    refreshInterval: interval (in seconds) after which the cloud is automatically refreshed
    --->
    <c:wordCloud objectType="account" 
                 records="{!account.id}" 
                 fields="Name,Type,Ownership,Description"
                 lowerbound="1"
                 skipwords="and,an,any,so,or,are,the,to,a,of,for,as,i,with,it,is,on,that,this,can,in,be,has,if"
                 grid="8"
                 factor="0"
                 normalize="false"
                 font="Futura, Helvetica, sans-serif"
                 shape="triangle"
                 width="100%"
                 height="400px" 
                 backgroundColor="black"
                 colorTheme="light" 
                 autoRefresh="false" 
                 refreshInterval="15" />
</apex:page>

Save that page and add it to the page layout of the account. Put a nice big blob of text in the description of an account and see what happens. For my example I figured it would be fitting to copy and paste text from the frequency analysis page of wikipedia. The result looks like this.

Result of WordCloud

Pretty cool eh? Anyway this was just something I threw together in an hour or two, hopefully it’s useful to someone out there. Let me know what ya think!

Convert a string into a Salesforce Fieldname using regular Expressions

Hey all,

Another quick tip sample code type of post here (and probably not even ‘GOOD’ sample code, but at least functional). I had a requirment recently where I needed to create a dynamic query that will be built from some other values. Those values would represent fieldnames that needed to be queried but might not match exactly. So if the value the user selected was ‘My Big! FUN! Account (Acme LLC)’ that wouldn’t work in a query as field name because of all the special chars, spaces, etc. So I needed a bit of logic to clean up strings and attempt to convert them into valid Salesforce field names. I know this isn’t exactly the best approach, but this is within a limited set a values for an internal app, so it’s an understandable trade off. Anyway, enough excuses, let’s see some code.

//regular expressions to be used for replacing
string specialCharPatterns = '[^\\w]+';
string multiUnderscorePattern = '_+';

string fieldName = 'My Str!ng Th@t doesn't represent a valid F!eld   name(!)';

//replace special chars with underscores, and multiple underscores with one
fieldName = fieldName.replaceAll(specialCharPatterns,'_').replaceAll(multiUnderscorePattern,'_');

//remove leading underscores
fieldName = fieldName.left(1) == '_' ? fieldName.substring(1) : fieldName;

//remove trailing underscores
fieldName = fieldName.right(1) == '_' ? fieldName.substring(0,fieldName.length()-1) : fieldName;

//append custom field suffice
fieldName = fieldName + '__c';

string queryString = '';

queryString = 'select id, ' + fieldName + ' from Account';

list<sObject> sobjects = database.query(queryString);

if(!sobjects.isEmpty())
{
    objectValue = (string) sobjects[0].get(fieldName);
}

So there ya go. A simple way to take a field, clean it up into a fieldname plug it into a query, and as a bonus extract the result. Have fun!

Follow

Get every new post delivered to your Inbox.

Join 574 other followers