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.
2) HTML5 spec gives us a postMessage API to frames from different domains to pass information to each other.
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.
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.
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!