Wednesday, September 24, 2014

Data Dictionary Utility

Surely you've had the need to generate a spreadsheet with object and field details for someone to use for a data conversion or integration, right?

I had previously posted a solution involving a Cast Iron orchestration that could go object by object within your org and write to another custom object some details like field name, field data type, length, etc.

Here is another way, that I'll make as a unmanaged package in a future post.  In the interim, here is the recipe:

1. Create a custom object to hold the definitions.  In the anonymous apex below, my reporting object, ObjDef__c has fields like Object_Name__c, Field_Name__c, Data_Type__c, Length__c.

2. Execute the following anonymous apex for each of the objects you'd like to report on.  In my example, I have a custom object called 'Conference__c' that I want to define so that my business partner can create a data dictionary.

****************
//this is the object that you want to define
String type='Conference__c';

map<string, Schema.SObjectType> schemaMap = Schema.getGlobalDescribe();
Schema.SObjectType mySchema = schemaMap.get(type);
map<string, Schema.SObjectField> fieldMap = mySchema.getDescribe().fields.getMap();

//this is the object that you will report on
list<ObjDef__c> myDefs = new list<ObjDef__c>();

//loop through each of the fields on the object and create a record in your reporting obj
for (String fieldName: fieldMap.keySet()) 
{
    //this is the reporting obj
    ObjDef__c myDef = new ObjDef__c();
    myDef.Object_Name__c = type;
    myDef.Name = fieldName;
    myDef.Field_Name__c = string.valueOf(fieldMap.get(fieldName).getDescribe().getLabel());
    myDef.Data_Type__c = string.valueOf(fieldMap.get(fieldName).getDescribe().getType());
    myDef.Length__c = Integer.valueOf(fieldMap.get(fieldName).getDescribe().getLength());
    //add the rec to a list for insert
    myDefs.add(myDef);
}
//do the insert
insert myDefs;
**********************

3. Create a report on your custom object

Now, you may mess up at some point or maybe your schema has been updated.  No worries, just add a few lines to the anonymous apex to delete all of the rows in the reporting object that holds the invalid/outdated schema and re-run the script.

Wednesday, February 12, 2014

Hiding Tab Tools using JQuery

If you've logged into Salesforce a million times or more like me, you may have trained your eyes to ignore some of the subsections on the standard tabs.  For example, when you click on the Accounts tab, there is a Tools section with all kinds of goodies that you may not want your users to know they have the power to do.  I mean, does "Mass Delete Accounts" sound like a link you want someone to click on... ever?!



In most cases you can control access to these links by some profile permission or user permission.  For example, if you remove the "Modify All Data" setting on the profile, most of the links above go away. However, you may encounter a need to give someone a very broad permission like "Modify All Data" or "Transfer Records", but want to restrict their ability to see their access these tools.  One option to evaluate is using jquery to scrub out the links from the ui.  Borrowing some ideas from stackexchange, a little script like this to the home page (as an html component) has the ability to remove any link (or the entire section).  In my use case, I want to remove the ability to "Transfer Accounts" from this tab except by my power user:

******
<script type="text/javascript" src="/soap/ajax/27.0/connection.js"></script> 
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script type="text/javascript">
$j = jQuery.noConflict();
$j(document).ready(function()
{  
var sid = getCookie('sid');  
var server = "https://" + window.location.host + "/services/Soap/u/27.0";  
sforce.connection.init(sid, server);
var currentUser = sforce.connection.getUserInfo();   
if(currentUser.profileId != 'SOMEPOWERUSER')  
{  
var url = location.href;
var tabUrl = "/001/o";   
if(url.indexOf(tabUrl) !== -1)  
{  
$j('.toolsContentRight a').each(function() 
{  
if ($j(this).text() == 'Transfer Accounts')
{  
$j(this).text('');  
}  
 
});   
}     
}});
</script>

*******

Mind you, that this does not remove their permission to do these things.  It just makes it less obvious by removing it from the tab.

Preventing Your Form from Submitting Twice (or more!)

If you have a visualforce page that does any kind of update or insert and you're using a custom method on your controller, be mindful of your aggressive clickers!  They'll get you in trouble by submitting your form more than once, wreaking havoc on all of your hard work.

I just dealt with this and was surprised that I was not able to use the apex:actionstatus and facet combination that I typically use to disable buttons that initiate queries.  Turns out that the important detail is that the actionstatus tag requires us to rerender a component.  When this rerendering occurs, you potentially get another form submission.  It may have to do with the redirect my page was doing after the save.  The good folks over at the stack exchange saved my bacon once again.  Their approach to disable your button is the following:

Button click calls an actionfunction js.  The actionfunction does some javascript to disable the buttons using jquery, then calls the actionfunction component, which describes the controller method to run and what to do when the method completes.

I changed up the accepted stack exchange solution in two ways:

1. The button, while gray and disabled, still behaved like a button (it was still clickable).  I ended up using jquery to remove the btn class (which makes it clickable) and also restore the btn class, after any page messages/errors are displayed:

function buttonsEnabled(enabled) {
        // to disable the button
        if (enabled === false) {
            var $b = jQuery('.btn');
            $b.removeClass('btn');
            $b.addClass('btnDisabled');
            $b.toggleClass('btnDisabled', true).attr('disabled', 'disabled');
        } else {
            var $b = jQuery('.btnDisabled');
            $b.toggleClass('btnDisabled', false).attr('disabled', null);
            $b.removeClass('btnDisabled');
            $b.addClass('btn');
            
        } 
    }

2. If you have some validations on your page, you'll want to be sure you're re-rendering the messages tag in your action function.  In this example, my pagemessages has the id "msgs".

<apex:form id="form">
<apex:actionFunction name="doSomeWorkActionFunction" 
        action="{!mySave}" 
        oncomplete="buttonsEnabled(true);"
        rerender="msgs">
</apex:actionFunction>
<apex:pagemessages escape="false" id="msgs"/>
<apex:pageblock mode="Detail" id="pageblock" >
    <apex:pageblockButtons >
        <apex:commandButton id="mysavebutton" 
                  onclick="return doSomeWork();" value="Save"  />
        <apex:commandButton action="{!myCancel}" value="Cancel" immediate="true" />
    </apex:pageblockButtons>

Wednesday, January 22, 2014

Lost Data?

I have a client who recently discovered that they had an inbound integration overwriting some important opportunity data in Salesforce.  Unfortunately, the field that was being overwritten was not a field that had tracking turned on as they had reached their limit of 20 fields.

Salesforce has put together a nice checklist of things you can do to try to recover lost or deleted data.  For my client, the suggestions weren't options to consider because the data updates were incremental over a long period of time.

However, we didn't give up, and were able to recover some of the data from workflow emails!  The attribute that they thought they had lost was part of the Won email that was sent out and because the administrator was copied on these emails, we just had to figure out a way to extract the data we wanted to recover.  Turns out if you can get your email into Outlook, Access has a way to import email messages.  Once the emails are in Access, a few little queries can fetch the data you might need.  For us, we just needed the link to the opportunity and a value from the email template.  Once the query fetched the data we wanted, a little scrubbing in Excel cleansed the rows for import.

TL;DR: you might be able to recover lost data from your workflow emails!

Tuesday, January 7, 2014

Salesforce1 list views and Flexipages

Issue: List Views are not displayed by default in Salesforce1 tabs

Part of the Salesforce1 mobile app is a design decision that makes visible in the mobile app only the list views that a user had previously run in the Salesforce.com application.  We were caught by surprise with this "feature" when we rolled out a new mobile app on Salesforce1.  Our mobile users didn't see any of the list views we had created for them in the main application, because they are primarily mobile users, and it was only through trial-and-error did we discover what was going on.  We were able to corroborate our experience when we saw this idea.

Some of the possible workarounds we explored:

Pinned List Views:
While we knew that this would be self-correcting over time, we thought about some alternatives that were mentioned in that thread.  One suggestion was to use "pinned list views", or PLVs, which I had not heard of before.  PLVs were introduced in the Winter 14 release as part of the Service Cloud console. We're not Service Cloud users so it was not a viable workaround for us.

Flexipages:
The other idea, that was suggested was to implement "Flexipages".  Flexipages were introduced in the Salesforce1 developer guide, chapter 15, as "a middle ground between page layouts and visualforce pages".  After working with these, I'm not sure I agree.  I'm not even sure I'd say that this feature isn't a beta as the procedure for developing and deploying feels half-baked.  Here are some of the key takeaways:

1. There is neither a gui section in the Setup menu for flexipages nor a way to create a flexipage using the developer console.  I did my "dev" with Notepad.  I took the example xml and changed it for my custom object in my text editor and saved it as .xml.

2. My flexipage just need two things: an "All" items list view and a "Recent" items list view.  The docs are unclear how you achieve this but basically you reference your application's list views by name.  For example, if you have a list view in Salesforce.com with a Name "All_Records", your flexipage.xml should have something like this:

<componentInstanceProperties>
<name>filterName</name>
<value>All_Records</value>

</componentInstanceProperties>

3. According to the docs, the flexipages are deployable from the force.com ide.  I upgraded my ide and did not see any reference to flexipages.  Could be an ide update issue but for the sake of time I ended up using workbench to deploy my package.  Just zip up your package and include an updated package.xml like what is described in the api documentation:

<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
    <fullName>Travel, Inc.</fullName>
    <types>
        <members>TravelIncFlexiPage</members>
        <name>CustomTab</name>
    </types>
    <types>
        <members>TravelIncFlexiPage</members>
        <name>FlexiPage</name>
    </types>
    <types>
        <members>TravelIncQuickActions</members>
        <name>QuickAction</name>
    </types>
    <version>29.0</version>
</Package>

4. It took a few tries for me to deploy the package in workbench but when it finally went through, the last few steps to expose the page in the Salesforce1 app were straightforward.  Just add a flexipage tab and then add the tab to your mobile navigation as described in the developer guide.

5. Any updates to your flexipage require you to reload your flexipage xml.  Ugh.  Hopefully you remember where you backed it up and be sure to buy your admin a beer as they're going to hate deploying this.