Friday, August 5, 2011

Using static resources with Salesforce custom Javascript buttons

Having hacked around with Salesforce's custom button that execute javascript, I quickly found myself needing to reuse code across other buttons within our CRM.

Salesforce has a Static Resources repository where you can store images, java script, even zip and jar files. Accessing said resources from the likes of Visualforce pages is a breeze;

Unfortunately, the $Resource variable has not been made available for use in custom buttons. However, we don't really need that variable to get a static resource into our Custom Button. All static resources are accessible via the URL in your salesforce org.

If you navigate to the Static Resources in your salesforce org, and click on the View File link;
You will see that the resource is accessible by a link as follows;

You could include this file directly into your custom button as follows:
but the maintainability of this approach will be extremely poor. If you play around with the URL a little more, you will find that the static resource is also available if you remove the string of digits, like this:
however, this also will cause problems.

See Salesforce does a lot of caching in order to allow their systems to scale. The long string of digits is a cache buster. Every time you update your static resource in Salesforce, a new string of digits will be generated. In fact, the string they use is actually the date & time that you created or updated your static resource represented in Unix Epoch Time and padded out with three trailing zeros.

The $Resources variable takes care of all this magic for us, when used in Visualforce pages, but as it is left out from custom buttons, we need to get creative!

This task would be especially simple if we had some useful time/date functions as part of the Salesforce validation functions, but no such luck. One may think that we could use Javascript date/time functions, but not so fast! The {!REQUIRESCRIPT()} syntax is pre-processed by salesforce, long before it touches your browsers javascript engine.

So, we are left with creating a MacGyver-esque cash buster with the tools that we have. The static resources urls expect to receive a string 17 digits. No letters, hyphens or colons. The closest we can come to is NOW() which will give you a string that looks something like this: '2011-08-06 01:42:00Z' If we strip away the non numeric characters, we get this 20110806014200 which is 14 characters in length. Recall that the URLs expect 17 characters. Using the Salesforce substitution functions, we can create a valid cash buster. Behold the following monstrosity;

{!REQUIRESCRIPT('/resource/' & LEFT(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(TEXT(NOW()),':',''),'-',''),' ',''),10) & '000/jbUtils')}

Now, every time a user visits the page with your custom button, because NOW() always is a new, numerically larger string, the user will always get the freshest-cache-busted static resource available, and you are fee to reuse javascript code in all of your custom salesforce buttons!

Some words of warning; This is a hack, and if this method was to upset our kind Salesforce overlords, we could find the method breaking in the night. Hopefully, said overlords will find our cache-busting-ingenuity charming & cutesy, and reward us by giving us $Resources for our lowly custom buttons...


Krešimir Tonković said...

It may be a new development, now the cachebuster number does not have to be of any specific length. So the LEFT and 000 parts can be omitted.

Mike said...

This is fantastic. I was facing this exact problem. I was going to tell all 1000+ users they needed to clear their cache to get an update to the JS files. You just saved my a**!

Sanket said...

I need to get to working from managed package and it doesnt work from there. Any suggestions

Ankit Sheth said...

This is really cool hack, I had similar issue when bundling custom button in managed package. The code inside button was visible to user so thought to move the JS to static resource (this too can be seen by using console etc but that's bit techie thing). I will try this approach to see if its working in managed package or not. Anyhow thanks for the pointer.

Jev said...

@Sanket, no, I have never had to work with managed packages, so it's not something I have explored.

Thanks @Ankit Sheth, glad it was useful. If you get it working in a managed package, then let us know here, but see my warning belo... ;)

@Kresimir, I think you are right, but I wanted to keep it the same length in case Salesforce tighten up the rules on this thing.

Remember that this is a complete hack, if you make use of it, your code is at the mercy of Salesforce. If they change the behaviour of this, they have no obligation to tell you, so if you put this in a managed package, you run the risk of having your package break in the field.
Good luck!

Thomas Gagne said...

So my situation is I need to call a local REST service from inside a Javascript button.

Ultimately, I was able whittle-down the button javascript to two {!REQUIRESCRIPT(..)} statements then a call my function.

Thanks for the help.

Anonymous said...

i have a doubt...

Is this possible??