Oh my god. It's full of code!

Posts tagged “Visualforce


Automatic Resize of Embedded/Inline Visualforce pages

I’m pretty sure at one time or another, most of us developers have made a sweet inline visualforce page that lives on the object detail page. Problem is since it’s all dynamic and cool we don’t know how ‘tall’ the content might end up being. We are basically stuck with having to guess at a decent height for the VF page and enable scrollbars, which kind of blows. Well not anymore (some exceptions apply)! Expanding on my previous post about using HTML5 postmessaging to move data between embedded visualforce pages and the standard salesforce domain I’ve come up with a first release of a ‘framework’ of sorts (term used very liberally). First an example. Lets check out what I’m talking about using my wordcloud app to demonstrate.

BEFORE

Scrollbars are Sad

 

AFTER
Woot woot no scrolling :D

So how did I accomplish this? The trick, as you may be able to guess by now is the HTML5 PostMessaging feature that I wrote about. By enabling communication between the frame (the visualforce page) and the parent (the detail page) it’s fairly simple to have the frame report it’s size to the parent, and have the parent adjust the height of the frame accordingly. Of course that isn’t everything my framework can do, oh no. It allows for passing of arbitrary commands/data between the two, so you can extend the functionality to pass any kind of commands/dom manipulations you might want between them. Of course the caveat is that this only works on newer browsers since postMessaging is fairly new. Also you’ll need to enable the sidebar on all pages, since that is where the proxy component lives. If you want to play with this install my alpha package from

https://login.salesforce.com/packaging/installPackage.apexp?p0=04ti0000000TY0J

Then include the home page component in the narrow/sidebar and enable the sidebar for all pages (setup->user interface->Show Custom Sidebar Components on All Pages). After that you just include these two components on any page you want to resize itself when embedded. The first component is the core piece that talks to the home page component. The second one is just some code that utilizes that framework to cause the iframe to resize. I’d put them at the bottom of your visualforce page.

<c:vfProxy />

<c:vfProxy_autoHeight />

I’m planning on possibly adding more ‘plugins’ for my framework to do other common handy things so if you have any ideas for potential plugins let me know.


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!


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.


Dynamic PDF Generator

I recently had a requirment where a list of any kind of sObject could be given to a visualforce page, and it should spit out a PDF report of those objects. The fields returned could possibly be defined by a field set, passed in the URL directly, or I could get passed nothing and would just have to query for all fields on the object. It was decided that the best course of action was to write a nice re-useable apex class that can handle these requirements and use the visualforce renderas attribute to make it easy to generate printable reports. You can easily rig up a custom button on a list view to get the checked elements and pass them into the exporter page as well to basically allow exporting from any list view. The following is the first draft of said functionality.

4/03/2013 EDIT: Thanks to a good tip by Cal Smith I changed how the visualforce page outputs the content and it seems to be much faster and probably safer too. I also included two new params for the exporter. You can now provided a field to order by by specifying order_by in the url. Also, if you want the records returned in the same order the ids were provided in the url you can specify return_in_order=true. This is probably slow on large data sets but in cases where your users may have put records in the order they want you can then pass the ids in that order to the controller and the PDF will be generated with the same order. Kinda a nice feature I thought.

4/05/2013 EDIT: I added the force_download param and filename params to allow you to force the user to download the file and specify a name for the downloaded file instead of letting them view it in their browser. Not totally sure why someone might want this, but it was a request I got and it was fairly easy to add.

The Apex Class

/*
Name: queryGenerator
Author: Daniel Llewellyn
Date: 4/02/2013
Description: Meant to be invoked by a visualforce page. This class can take url params to query for a list of any kind of
             sObject. Those sObjects can then be used to power user interface elements. By passing in a list of Ids and an
             option field set, this class is able to determine the object type, find a matching field set, use a default field set
             or if none is specified query for all fields defined on the object. Useful for generating lists of sObjects when the type
             of object, and desired fields is not known ahead of time.

URL Params:
name            type         req      description
--------------------------------------------------------------------------------------------------------------------
ids:            csv of ids   true     a list of sObject Ids seperated by commas. The objects to include in the return query
fields:         string       false    a comma seperated list of fields to include. Takes precidense over fieldSet if specified.
fieldSet:       string       false    the name of a fieldset to use to determine which fields to include. Used if fields param not specified. If both are null, all fields are queried.
order_by        string       false    the name of a field on the object to order the results by
return_in_order boolean      false    should the results be returned in the same order they were provided in the URL? Overrides the order_by param if set to true.
force_download  boolean      false    should the PDF file be forced to donwload instead of displayed in the browser window?
filename        string       false    the name to assign to the downloaded file if force_download is set to true. Defaults to object label + ' report.pdf' Do not include .pdf. It is appened automatically.

Gotchas:
Due to the way the query is built (filtering by a list of Ids) you can only get probably about 500 records max before the query length gets too long.
Shouldn't be a big deal though, a report of more than 500 records starts to get kind of meaningless most of the time. It will attempt to gracefully handle
any errors and return them nicely to the user.

*/

public class queryGenerator
{
    //Params. Can be used in your visualforce page to customize report data.
    public Schema.SObjectType OBJECT_SCHEMA_TYPE{get;set;}
    public string OBJECT_TYPE_NAME{get;set;}
    public string OBJECT_TYPE_LABEL{get;set;}
    public string ORDER_BY{get;set;}
    public boolean RETURN_IN_ORDER{get;set;}
    public list<string> OBJECT_FIELDS{get;set;}
    public map<string,string> OBJECT_FIELD_MAP{get;set;}
    public list<id> OBJECT_IDS{get;set;}
    public list<sobject> OBJECTS{get;set;}
    public integer RECORD_COUNT{get { 
        return objects.size();
    }set;}

    public queryGenerator(){

        try
        {

            OBJECT_FIELD_MAP = new map<string,string>();

            //get the list of ids to query for. We expect them to come in a url param called ids, and they should be
            //comma seperated. Since we know that, we can split them based on , to get a list of ids.
            if(ApexPages.currentPage().getParameters().get('ids') == null)
            {
                throw new applicationException('Please include a list of a comma seperated ids to query for in the url by specifying ?ids=id1,id2,id3 etc');
            }
            OBJECT_IDS = ApexPages.currentPage().getParameters().get('ids').split(',');

            //use the ids getSObjecType method to figure out what kind of objects these are we got passed. 
            OBJECT_SCHEMA_TYPE = OBJECT_IDS[0].getSObjectType(); 

            //caching describe results makes for faster iteration
            map<string,Schema.sObjectField> fieldMap = OBJECT_SCHEMA_TYPE.getDescribe().fields.getMap();

            for(Schema.SObjectField field : fieldMap.values())
            {
                OBJECT_FIELD_MAP.put(field.getDescribe().getName(),field.getDescribe().getLabel());
            }

            //get the name of this object type
            OBJECT_TYPE_NAME = OBJECT_SCHEMA_TYPE.getDescribe().getName();

            OBJECT_TYPE_LABEL = OBJECT_SCHEMA_TYPE.getDescribe().getLabel();

            //get the list of fields we will query for and display
            if(ApexPages.currentPage().getParameters().get('fields') == null)
            {
                OBJECT_FIELDS = getObjectQueryFields(OBJECT_SCHEMA_TYPE, ApexPages.currentPage().getParameters().get('fieldset'));    
            }
            else
            {
                OBJECT_FIELDS = ApexPages.currentPage().getParameters().get('fields').split(',');
            }
            //set the order by statment. If no order by is specified, just tell it to order by Id to prevent a syntax error
            if(ApexPages.currentPage().getParameters().get('order_by') != null)
            {
                ORDER_BY= ApexPages.currentPage().getParameters().get('order_by');   
            }
            else
            {
                ORDER_BY = 'Id';
            }            

            RETURN_IN_ORDER = false;
            if(ApexPages.currentPage().getParameters().get('return_in_order') != null && ApexPages.currentPage().getParameters().get('return_in_order') == 'true')
            {
                RETURN_IN_ORDER = true;   
            }       

            OBJECTS = getSojects();   

            if(ApexPages.currentPage().getParameters().get('force_download') != null && ApexPages.currentPage().getParameters().get('force_download') == 'true') 
            {
                string fileName = 'Report of '+OBJECT_TYPE_LABEL+'.pdf';
                if(apexPages.currentPage().getParameters().get('filename') != null)
                {
                    fileName = apexPages.currentPage().getParameters().get('filename') +'.pdf';
                }
                Apexpages.currentPage().getHeaders().put('content-disposition', 'attachemnt; filename='+fileName);
            }    

        }
        catch(exception ex)
        {
            //catch and return errors. Most often will happen from a bad Id of fieldname being passed in.
            system.debug('\n\n\n------Error occured during page init!');
            system.debug(ex.getMessage() + ' on line ' + ex.getLineNumber());
            ApexPages.addmessage(new ApexPages.message(ApexPages.severity.WARNING,ex.getMessage() + ' on line ' + ex.getLineNumber()));

        }
    } 
    //this method will be invoked by a visualforce page. It will determine the sObject
    //type by examining the Ids passed in the ids param. Once it knows the object type it will
    //then attempt to find a locate the specified fieldset if one was passed in the URL. If no fieldset
    //was provided, then it will query for all sObject fields.

    public list<sobject> getSojects()
    {
        list<sobject> queryResults;

        //lets get a list of fields to query for by using the getObjectQueryFields method. We will pass in the object type
        //and the fieldset url param (which may be null, but that doesnt matter).
        string queryFields = listToCsv(OBJECT_FIELDS);

        //build this query string
        string queryString = 'select ' + queryFields + ' from ' + OBJECT_TYPE_NAME + ' where id in :OBJECT_IDS ORDER BY '+ORDER_BY;

        if(queryString.length() > 10000)
        {
            throw new applicationException('Query too long ('+queryString.length()+'). Please reduce the number of ids or reduce the number of fields queried for to get the length under 10,000');
        }
        //run the query.
        queryResults = database.query(queryString);

        if(RETURN_IN_ORDER)
        {
            queryResults = sortQueryInOrder(OBJECT_IDS, queryResults);
        }
        return queryResults;
    }

    //takes the list of sObjects and sorts them in the order they were passed in the URL. This allows for a custom sorting order to be passed in
    //without having to make use of the SOQL order by clause which may not be robust enough to handle the types of sorts desired.
    //WARNING THIS IS PROBABLY PRETTY DAMN SLOW!
    public list<sObject> sortQueryInOrder(list<id> objectOrder, list<sObject> objects)
    {
        map<id,sObject> objectMap = new map<id,sObject>();
        list<sObject> sortedList = new list<sObject>();
        for(sObject obj : objects)
        {
            objectMap.put((id) obj.get('id'), obj);
        }

        for(id objId : objectOrder)
        {
            sortedList.add(objectMap.get(objId));
        }
        return sortedList;

    }
    //takes an sObject type and optional name of a fieldset for that sObject type (can be null). Returns a list
    //of strings of fields to query for either based on the fieldset, or by finding all sObject fields if no fieldSet
    //is specified, or a matching fieldSet can not be found.
    public list<string> getObjectQueryFields(Schema.SObjectType objectType, string fieldSetName)
    {
        set<string> fields = new set<string>();
        Schema.FieldSet thisFieldSet = null;

        //first any fieldsets that are defined for this object type. It is possible this might be empty.
        Map<String, Schema.FieldSet> fieldSetMap = objectType.getDescribe().fieldSets.getMap();  

        //check to see if the user passed in a field set, and if so, does it exist? 
        //if so, use that fieldset. Otherwise, use all fields on the object
        if(fieldSetName != null && fieldSetMap.containsKey(fieldSetName))
        {
            thisFieldSet = fieldSetMap.get(fieldSetName);
            //now that we know what field set we are using we have to iterate over it and get it feildsetmembers
            //and add each field into the query string.
            for(Schema.FieldSetMember f : thisFieldSet.getFields())
            {
                fields.add(f.getFieldPath());
            }            
        }             

        //if there are no field sets defined for this object, then lets just query for all the fields
        else
        {
            fields = getObjectFields(objectType);            
        }

        //return our variable that contains a properly comma seperated list of all the fields to query for.
        list<string> fieldList = new list<string>();
        fieldList.addAll(fields);
        return fieldList;
    }

    //a simple possibly overly abstracted method to get the fields on an object
    public set<string> getObjectFields(Schema.SObjectType objectType)
    {
        return objectType.getDescribe().fields.getMap().keySet();
    }

    //takes a list of strings and returns them in a comma seperated fashion, suitable for feeding into a query.
    public string listToCsv(list<string> stringList)
    {
        string itemList = '';
        for(string thisString : stringList)
        {
            itemList += thisString+',';
        }
        itemList=itemList.substring(0,itemList.length()-1);
        return itemList;
    }

    @isTest
    public static void testQueryGenerator()
    {
        //setup our test account
        Account testAccount = new Account();
        testAccount.name = 'My Test account';
        testAccount.billingStreet = '1234 Test Street';
        testAccount.billingState = 'NY';
        testAccount.billingPostalCode = '55555';
        testAccount.billingCountry = 'USA';

        insert testAccount;

        test.StartTest();

        PageReference pageRef = Page.exportPdf;
        Test.setCurrentPage(pageRef);

        //run it with no ids. It will come back with no records since it will error. Since the error gets caught
        //we don't need to try/catch here though.
        queryGenerator qg = new queryGenerator();

        //run test with nothing but ids specified. This will make it query for all fields
        ApexPages.currentPage().getParameters().put('ids', testAccount.id);        
        qg = new queryGenerator();

        //make sure it found our account
        system.assertEquals(1,qg.RECORD_COUNT);
        system.assertEquals(testAccount.name,(string) qg.OBJECTS[0].get('name'));

        ApexPages.currentPage().getParameters().put('fields', 'name,id,billingStreet');        
        qg = new queryGenerator();        
        //make sure it found our account
        system.assertEquals(1,qg.RECORD_COUNT);
        system.assertEquals(testAccount.billingStreet,(string) qg.OBJECTS[0].get('billingStreet'));

        ApexPages.currentPage().getParameters().put('order_by', 'name'); 
        ApexPages.currentPage().getParameters().put('return_in_order', 'true'); 
        ApexPages.currentPage().getParameters().put('force_download', 'true');
        ApexPages.currentPage().getParameters().put('filename', 'My PDF file');
        qg = new queryGenerator();       

    }
    class applicationException extends Exception {}
}

The ExportPDF visualforce Page

<apex:page controller="queryGenerator" renderAs="pdf"  standardStylesheets="false">
<head>
  <style>
    @page {
        size:landscape;
        margin : .5in;
        @top-center {
            content : element(header);
         }

        @bottom-left {
            content : element(footer);
        }

    }
    table
    {
        width:100%;
    }
    @bottom-left {
        content : element(footer);
    }
    div.footer {
        position : running(footer) ;
    }    
  </style> 
</head>
    <apex:pageMessages></apex:pageMessages>
    <h1>Report of {!OBJECT_TYPE_LABEL} ({!RECORD_COUNT} Records)</h1>

    <table>
        <tr>
            <apex:repeat value="{!OBJECT_FIELDS}" var="FieldLable">
                <apex:outputText><th>{!OBJECT_FIELD_MAP[FieldLable]}</th></apex:outputText>
            </apex:repeat>        
        </tr>

        <apex:repeat value="{!OBJECTS}" var="rec">
            <tr>
                <apex:repeat value="{!OBJECT_FIELDS}" var="FieldLable">
                    <apex:outputText><td>{!rec[FieldLable]}</td></apex:outputText>
                </apex:repeat>
            </tr>
        </apex:repeat>
    </table>

    <div class="footer">
    <apex:outputText value="The Date: {0,date,MMMMM dd, yyyy 'at' hh:mm a}" styleClass="footer" >
        <apex:param value="{!NOW()}" />
    </apex:outputText> 
    </div>    
</apex:page>

Sample List View Button

window.open('/apex/exportPdf?ids='+ {!GETRECORDIDS($ObjectType.YOUR_OBJECT_TYPE)}+'&fieldset=YOUR_FIELD_SET_NAME_HERE&order_by=name&return_in_order=false','1364931211178','width=700,height=500,toolbar=0,menubar=0,location=0,status=1,scrollbars=1,resizable=1,left=0,top=0')

You’ll need to replace the $ObjectType.YOUR_OBJECT_TYPE and the fieldset=YOUR_FIELD_SET_NAME_HERE in the list view button. Or you can just remove the fieldset part entirly, or replace it with a ‘fields’ attribute where you can specify a comma separated list of fields to query for. You’ll probably want to play with the formatting of the report a little but, but I’ll leave that as an exercise to the reader. Hopefully this helps someone out there.


Displaying and Caching Salesforce Attachment Images in Sites

This time around we are going to be talking about images. How to store them, how to query for them, display them and cache them, in Salesforce, using javascript remoting. We’ll be building a simple application using jQuery, Salesforce and Apex to query for attachments, display them and cache them reduce load times and overhead.

Abstract:
First off, I’m having a bit of a hard time organizing all my thoughts on this topic. It’s kind of big, so please forgive if I skip around a bit. Feel free to ask for clarifications in the comments. So let’s say you are building an application to be hosted on Salesforce. Your application is going to need to publicly accessible (so you are going to be using sites) and the application is going to need to show images that may change frequently and hence would be configured by some non developer types. Your application is going to show all the products you have available, along with pictures of said products.

There is of course many ways you can go about storing your images and relating them to your products but the most straight forward option is to use the notes and attachments feature. That would allow users to easily manage the pictures related to each opportunity without having to go to some central picture repository, or building any additional relationships between objects or URLS. The problem of course is that attachments don’t have a publicly accessible URL to them. You can view them from within Salesforce but you don’t have any way to display them on a site. This could be an issue. Not so fast!

Images as Data
You know those images you uploaded to Salesforce via the attachments feature exist somewhere on Salesforce servers. We also know that Salesforce hates file storage and loves databases. It should come as little surprise that the attachments are actually stored in a table as blob data. That data can be queried for just like any other data. Another little know thing is that in HTML while the img tag normally has it’s src attribute set to a URL, it can in-fact accept base64 encoded image data by specified the data type (). Perhaps we can put all this information together into something useful. Yes, yes we can.

Getting The Image Data
So go ahead and get a visualforce page and controller set up. I’m calling mine productList and productListController respectively. Let’s get the code for our controller in place. Copy and paste this.

global class productListController
{

    //get all the products in the org along with their attachments.
    @remoteAction
    global static remoteObject getProducts()
    {
        remoteObject returnObj = new remoteObject();

        try
        {

            list<Product2> products = [select 
                                                Name,
                                                ProductCode,
                                               Description,
                                               Family,
                                               isActive,
                                               (SELECT Attachment.Name, Attachment.Id FROM Product2.Attachments)
                                               from product2
                                               where isActive = true];
            returnObj.sObjects = products;
        }
        catch(Exception e)
        {
            returnObj.success = false;
            returnObj.message = 'Error getting products';
            returnObj.data = 'Error Type: ' + e.getTypeName() + ' ' + e.getCause() + ' ' + ' on line: ' +e.getLineNumber(); 
        }

        return returnObj;       
    }

    //gets a single attachment (photo) by id. The data is returned as a base64 string that can be plugged into an html img tag to display the image.
    @RemoteAction
    global static remoteObject getAttachment(id attachmentId)
    {   
        remoteObject returnObj = new remoteObject();
        try
        {
            list<Attachment> docs = [select id, body from Attachment where id = :attachmentId limit 1]; 
            if(!docs.isEmpty())
            {
                returnObj.data = EncodingUtil.base64Encode(docs[0].body); 
            }    
        }
        catch(exception e)
        {
            returnObj.success = false;
            returnObj.message = e.getMessage();
            returnObj.data = 'Error Type: ' + e.getTypeName() + ' ' + e.getCause() + ' ' + ' on line: ' +e.getLineNumber();        
        } 
        return returnObj;    
    }   

    global class remoteObject
    {
        public boolean success = true;
        public string message = 'operation successful';
        public string data = null;
        public list<sObject> sObjects = new list<sObject>();
    }    
}

As you can see it’s a pretty simple little controller. We have one method that gets a listing of all the products and the Id’s of the associated attachments using a subquery. That prevents us from having to run another query to get the attachment Id’s. The second function takes a specific attachment id and will return an object with the base64 encoded version of the image. That’s what I was talking about earlier. You can query for an attachment and get it’s raw binary/blob data. Then you can base64 encode it for transfer from the controller back to the requesting page. With that you can get the image data out Salesforce and to your public application.

This does introduce another problem though. Caching. Normally images would be cached by the browser when they are loaded. It uses the filename to create a cached version of the image so next time your browser needs to load it it can just pull it off the hard drive instead of across the internet. The problem with base64 images is they can’t really be cached easily. By the time you have enough data to find it in the cache, you already loaded the whole thing, totally defeating the entire point of the cache. How can we fix this? Caching is too important to just skip in most applications, but yet we need to use base64 encoded images in our app.

Local Storage
With HTML5 we now have something called local storage. Basically it lets us store just about anything we want on the users computer for use at a later time. Basically cookies on steroids. Also where as cookies had to be small little text files, local storage gives us much more flexibility with size. We can leverage this to build own our cache.

Here is the game plan. We’ll run our query to find all the products. We’ll loop over each product we find and create a create an img tag that contains the ID of the image/attachment that needs to go there. After that, we’ll loop over each image tag and populate it with the image. We’ll check to see if we have a local storage item with the ID of the image/attachment. If so, we’ll load that data from the local cache. If not, we’ll make a remoting call to our Apex getAttachment method, and cache the results with local storage then load the data into the img tag. Here is what that looks like.

<apex:page controller="productListController">
    <head>
    <title>Product List</title>
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.js"></script>

    <script>
        $(document).ready(function() {
            getProducts(function(){
                $('.cacheable').each(function(index){
                    var img = $(this);
                     getImage($(this).attr('id'),function(imageId,imageData){
                         $(img).attr('src', 'data:image/png;base64,'+imageData);
                     });
                });               
            });            
        });  

        function getProducts(callback)
        {
                    Visualforce.remoting.Manager.invokeAction(
                        '{!$RemoteAction.productListController.getProducts}',
                        function(result, event)
                        {
                            if (event.status && (result.success == true || result.success == 'true')) 
                            {    
                               var html='';
                               for(var i = 0; i<result.sObjects.length;i++)
                               {
                                   var imageId = 'default image id here';
                                   if(result.sObjects[i].hasOwnProperty('Attachments'))
                                   {
                                        imageId = result.sObjects[i].Attachments[0].Id;
                                   }
                                   html += '<li><img class="cacheable"  id="'+imageId+'">'+result.sObjects[i].Name+'</li>';

                               }
                               $('#products').html(html);
                               callback();
                            } 
                            else
                            {
                                $("#responseErrors").html(event.message);
                            }
                        }, 
                        {escape: true});                   
        } 

        function getImage(imageId,callback)
        {
             var imageData;

              if ( localStorage.getItem(imageId))
              {   
                console.log('Getting image from local storage!');
                imageData = localStorage.getItem(imageId);
                callback(imageId,imageData);    
              }
              else 
              {
                   console.log('Getting image remote server!');
                    Visualforce.remoting.Manager.invokeAction(
                        '{!$RemoteAction.productListController.getAttachment}',
                        imageId,
                        function(result, event)
                        {
                            if (event.status && (result.success == true || result.success == 'true')) 
                            {    
                                 imageData = result.data;
                                 localStorage.setItem(imageId,imageData);      
                                 callback(imageId,imageData);    
                            } 
                            else
                            {
                                $("#responseErrors").html(event.message);
                            }
                        }, 
                        {escape: true});                   
              }      
        } 
    </script>
    </head>

    <body>
            <ul  id="products"></ul>
    </body>            
</apex:page>

So if you are familiar with jQuery and callbacks it’s pretty easy to make sense of what’s going on here. Once the DOM loads we are going to call the getProducts function. getProducts is going to use remoting to run the getProducts apex method. It will iterate over the results and create a list item for each product as well as that empty tag with the id attribute we talked about earlier. It also assigns the img tag the cacheable class so we can easily iterate over them once we are done. Once the looping and list building is complete, we call the callback functions. Since remoting requests are asyncronous we need to use callbacks when we only want to call one function when the other has completed first. Callbacks are a bit beyond the scope of this article, but just know that if we didn’t use them the get $(‘.cachable’).each() loop would run before the list had finished being populated.

So anyway getProducts finishes running and creating the list. Then comes the loop that uses jQuery to find any element that has the ‘cacheable’ class. For each element it finds, it calls the getImage() function on it, passing in the Id of that element. GetImage is where the cacheing magic happens. It will check to see if a local storage item exists with the id it gets passed. If so, it calls back with that content, if not, it queries Salesforce for an attachment with that id, creates a local storage element for it, and then again returns that content. The loop takes the returned content and sets the src tag of the img element with the base64 encoded data and boom! We have an image.

There you have it. Using Salesforce attachments to house images, using Apex and jQuery to query for them and display them, and HTML5 local storage to cache them. Pretty cool eh? I could write more, but I’m tired and I don’t feel like it. Hit me with questions if ya got em.


Apex Captcha with Javascript Remoting and jQuery

So at one time or another, we’ll likely all have to create a public facing form to collect data. We will also find about 2 seconds afterwards that it is getting spammed to hell. To stop the flood of crap, we have reCaptcha. An awesome little utility that will prevent bots from submitting forms. You already know what captcha is though, that’s probably how you found this post, by googling for apex and captcha. First off, there is already an awesome post on how to do with by Ron Hess (Here), but his approach is a bit complicated, and visualforce heavy. Of course being kind of anti visualforce, and the complexities of properties and all that, I made my own little approach. So here we go.

This is assuming you already signed up with reCaptcha. You can go here and sign up for recaptcha (yes you can just enter force.com as the domain)
After that, course add an entry for google to your remote sites in the admin setup under security. Disable protocol security.
Then create your visualforce page, and apex class. I called my class utilities, since this is kind of a re-usable function and I wanted to keep it generic.

Now put this crap in your controller. Also, your controller needs to be global (to use javascript/apex remoting)

@RemoteAction
    global static boolean validCaptcha(string challenge, string response)
    {
      boolean correctResponse = false;
      string secret = 'your recaptcha secret key here. Maybe make this into a custom setting?';
      string publicKey = 'your recaptcha public key here. Maybe make this into a custom setting?';
      string baseUrl = 'http://www.google.com/recaptcha/api/verify'; 

      string body ='privatekey='+ secret +  '&remoteip=' + remoteHost() + '&challenge=' + challenge + '&response=' + response + '&error=incorrect-captcha-sol';
      
      HttpRequest req = new HttpRequest();   
      req.setEndpoint( baseUrl );
      req.setMethod('POST');
      req.setBody ( body);
      try 
      {
        Http http = new Http();
        HttpResponse captchaResponse = http.send(req);
        System.debug('response: '+ captchaResponse);
        System.debug('body: '+ captchaResponse.getBody());
        if ( captchaResponse != null ) 
        {  
            correctResponse = ( captchaResponse.getBody().contains('true') );
        }          
       
      } 
      catch( System.Exception e) 
      {
         System.debug('ERROR: '+ e);
      }                             
      return correctResponse;
    }

    global static string remoteHost() 
    { 
        string ret = '127.0.0.1';
        // also could use x-original-remote-host 
        try
        {
            map<string , string> hdrs = ApexPages.currentPage().getHeaders();
            if ( hdrs.get('x-original-remote-addr') != null)
            {
                ret =  hdrs.get('x-original-remote-addr');
            }
            else if ( hdrs.get('X-Salesforce-SIP') != null)
            {   
                ret =  hdrs.get('X-Salesforce-SIP');
            }
        }
        catch(exception e)
        {
        
        }
        return ret;
    }

Ok, great, now your controller is ready. You just need to pass the right info and it will tell you if it’s right or wrong. Lets get a visualforce page set up to do that.

<apex:page controller="utilities" standardStylesheets="false" sidebar="false"  >

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" />


<script>
$(function() {

    $( "#validateButton" ).click(function(){
        
        validCaptcha(function(valid){
            if(valid)
            {
                $('#validationResultDiv').html('Valid Captcha!');
                
                //Do whatever here now that we know the captcha is good.
            }
            else
            {
                $('#validationResultDiv').html('Invalid Captcha Entered');
            }
           
        });           
    });
});

function validCaptcha(callback)
{
    var challenge = document.getElementById('recaptcha_challenge_field').value;
    var response = document.getElementById('recaptcha_response_field').value;

    utilities.validCaptcha(challenge,response, function(result, event)
    {
        if(event.status)
        {
           callback(result);
        }
    }, {escape:true});
}

</script>

<div id="captchaEnter" title="Form Submission Validation">
    <center>
    <script type="text/javascript" src="https://www.google.com/recaptcha/api/challenge?k=YOUR PUBLIC KEY GOES HERE DONT FORGET IT"></script>
    <noscript>
       https://www.google.com/recaptcha/api/noscript?k=YOUR_PUBLIC_KEY
     </noscript>  
     <div id="validationResultDiv"></div>   
     <button id="validateButton" class="inline">Submit</button>
       
     </center>
</div>


</apex:page>

Boom! Just that easy. Hook up an event handler to the submit button that runs the validCaptcha function. It will get the proper values, and send them to the apex class, which sends them to reCaptcha to verify. Once an answer comes back, it is passed into the callback function, which the can run whatever action you require. Don’t forget to replace the place holder public key in the script line above. Have fun!