2

I'm building a custom interface in Visualforce that uses dependent picklists. Since Apex doesn't expose information about dependent picklist values, I've had to resort to building that information on the client side and sending it up to my controller. The problem is that's expensive processing to do on every page load when the picklist options themselves will change very frequently. As such, is there a way to store/cache the information in SFDC so that I can update it only when it changes?

Approaches I've tried and ruled out:

  1. Custom Settings: this seemed like the obvious choice, but a JSON representation of my picklist data is significantly longer than the maximum field length.

  2. Static Resources: it's not possible to create Static Resources from Apex, so I'd have to manually update the resource when there are changes -- it wouldn't be possible to build a VF page to do this for me.

  • You can set the Cache attribute to true in page so that page is cached .developer.force.com/cookbook/recipe/… this site for more info – Mohith Shrivastava Dec 4 '12 at 5:56
  • Custom settings is a good choice .You need not fill in JSON.Platform will automatically convert into JSON.Paste your JSON i will help you how to generate same from custom settings – Mohith Shrivastava Dec 4 '12 at 5:57
  • We need not store JSON in custom settings .JSON can be easily generated through apex rest . – Mohith Shrivastava Dec 4 '12 at 6:00
7

Benj, since you're using the JavaScript Ajax Toolkit to get at the Dependent Picklist API, another option is to cache the information using JavaScript as well, by storing it in HTML5 Local / Session Storage, with a fallback either to Cookies or Custom Settings (as per Andrew Fawcett's 'striping' approach, which is a good solution as well) for older browsers that do not support HTML5. We've used this for caching calls to describeTabs, and it's worked great. HTML5 Session/Local storage is definitely the way to go looking forward, as it's lightning fast, gives you plenty of storage space, and is all client-side, so after the first call there's no work at all done in Apex.

Here's a great intro to HTML5 Local/Session Storage.

Here's the basic approach you'd need to do in Visualforce/JavaScript to get this going:

  1. Create some storage getter/setter methods that will do the hard work of checking for support of local/session storage and falling back to cookies if necessary.
  2. On page load, check your storage for a cached picklist dependencies key in your storage.
  3. If the cache exists, grab it! Otherwise, do a call to AJAX API to retrieve the info you need. Then, strip the info down to the bare minimum you need to store (the AJAX API calls often return a lot of needless bloat, I recommend parsing your responses down to just what you need), and then use JSON.stringify() to convert your data to JSON before storing it in your cache.

Part 1: Create your Local Storage Repo

// The canUseLocalStorage() function will let you know
// if HTML5 local storage is supported
var _canUseLocalStorage = 'nodata',
canUseLocalStorage = function(){
    if (_canUseLocalStorage === 'nodata') {
        try {
          return (_canUseLocalStorage = 'localStorage' in window && window['localStorage'] !== null);
        } catch (e) {
          return (_canUseLocalStorage = false);
        }
    } else return _canUseLocalStorage;
},
// Store stuff in local storage, if possible,
// otherwise fall back to cookies
localStorage = function(key,value) {
    // Can we use storage?
    var canUseStorage = canUseLocalStorage();
    return (
        // If we're RETRIEVING content...
        (typeof value === 'undefined') ? (
            // Use Local Storage
            (canUseStorage) ? window.localStorage[key] 
            // Use cookies
            : $.cookie(key,value)
        )
        // If we're STORING content
        : (
            // Use Session Storage
            (canUseStorage) ? (window.localStorage[key] = value) 
            // Use cookies
            : $.cookie(key,value)
        )
    );
};

Part 2: Check to see if your storage repo has a cached result

// Our key(s) will be called 'picklistCache_<objectName>_<fieldName>`
// So here's an example for the Account Type field
var cacheName = 'picklistCache_Account_AccountType',
    cacheResult = localStorage(cacheName);

// If we did NOT have such a result, 
// we'll need to query the AJAX API
if (!cacheResult) {
    if (sforce.connection.sessionId) {
        // Query the AJAX API for the Picklist Dependency info,
        // using the strategy in the following article:
        // http://iwritecrappycode.wordpress.com/2012/02/23/dependent-picklists-in-salesforce-without-metadata-api-or-visualforce/

        // ....
        // ANd stringify your result once you're done!
        cacheResult = JSON.stringify(cacheResult);

        // Once we've got our data, cache it!
        localStorage(cacheName,cacheResult);

    }

}

// Now use the result to do your client-side picklist dependency work!
if (cacheResult) {
     // Unpack our JSON result
     cacheResult = JSON.parse(cacheResult);

    // Render dependent picklists
}
  • Excellent idea to take advantage of HTML 5's local storage. – Mike Chale Dec 4 '12 at 12:41
  • Seems like this may be the best option, but even that seems like a heavier lift. – Benj Dec 5 '12 at 4:01
1

Custom Settings are ideal for this.

  • You can either create a List based Custom Setting and split your information accross multiple rows.
  • Or span your information accross multiple fields, if you have reasonable confidence on the maximum size. I used this second approach recently to store Ebay's API ID which is quite large but finite.

Either way the following should hopefully help you...

    // Custom setting fields have a max of 255, so we need to split this api token up
    eBayAPI__c ebayAPI = eBayAPI__c.getInstance();
    List<String> chunks = splitIntoChunks(EbayAPIToken, 255);
    ebayAPI.UserTokenPart1__c = null;
    ebayAPI.UserTokenPart2__c = null;
    ebayAPI.UserTokenPart3__c = null;
    ebayAPI.UserTokenPart4__c = null;   
    if(chunks.size()>0)
        ebayAPI.UserTokenPart1__c = chunks[0];                  
    if(chunks.size()>1)
        ebayAPI.UserTokenPart2__c = chunks[1];                  
    if(chunks.size()>2)
        ebayAPI.UserTokenPart3__c = chunks[2];                  
    if(chunks.size()>3)
        ebayAPI.UserTokenPart4__c = chunks[3];                  
    upsert ebayAPI;
    return null;

This is the helper function I used to split the string.

    private List<String> splitIntoChunks(String text, integer chunkSize)
    {
        // Util code to split up large strings
        List<String> chunks = new List<String>();
        integer offset = 0;
        while (offset < text.length())
        {
            integer partSize = Math.min(chunkSize, text.length() - offset);
            chunks.add(text.substring(offset, offset + partSize));
            offset += partSize;
        }
        return chunks;
    }       
  • This approach would be tedious to configure for very large strings that need to be stored (e.g. a 5000 character string, very small considering the size of the JSON-serialized representation of the Picklist Dependencies object, would require 20 textarea fields at 255 chars/per field). However, you can have up to 300 fields on a Custom Setting (as per the Limits docs), so this is theoretically doable. The "chunkifier" function will be really ugly, though. – zachelrath Dec 4 '12 at 12:58
  • Yes I think if your needing that level, I would use List based Custom Settings personally. The chunkifier can then be taught (or just assume field / row sizes) about the boundaries of the data and split across the rows or equally the columns (using Dynamic Apex to populate the fields) either way. – Andrew Fawcett Dec 4 '12 at 13:05
  • Yes that's true using Dynamic Apex you could easily iterate from 1...<n> over as many fields as needed on the CustomSetting record if they were named using a convention, e.g. CustomSetting__c.Data<n>__c. I've done something like this before for striping on textarea fields on regular SObjects, and it works fine. But I would still start by caching the data client-side before resorting to server-side. – zachelrath Dec 4 '12 at 20:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.