IFrame Solutions

May 3rd, 2010 § 1

IFrames get a bad rap. In the early days their cross-browser sup­port was spotty and even now, in­cor­por­at­ing them in mis­sion-crit­ic­al de­ploy­ments re­quires a good reas­on and a con­sidered ap­proach. But there are good reas­ons. And giv­en a per­fect storm of cap­ab­il­it­ies and lim­it­a­tions, an iframe can serve as the av­en­ue to a seam­less ex­per­i­ence where user in­ter­ac­tion would oth­er­wise be punc­tu­ated by popups, scroll­bars, or host switches.

In work with two sep­ar­ate vendors re­cently I was con­fron­ted with the chal­lenge of giv­ing logged-in users single-sign-on ac­cess to third party sites from with­in one of our ap­plic­a­tions. The en­vi­sioned solu­tion was the one above and the iframe’s very pur­pose in life is to en­able this sort of se­greg­ated mashup. For the seam­less ex­per­i­ence, however, we needed to be able to re-size the iframe con­tain­er to pop­u­lat­ing pages of vary­ing height while not break­ing the third party apps’ cap­ab­il­ity to dy­nam­ic­ally gen­er­ate new win­dows with con­tent.

In­tel­li­gent Re-siz­ing

There is one of rule of in­ter­net law that must be ad­hered to for the com­mu­nic­a­tions de­scribed be­low to be al­lowed: both host and iframed pages must reside un­der the same gen­er­ic do­main. (Host page, for ex­ample, at ht­tp://subdomain1.do­main.tld and iframed pages at ht­tp://subdomain2.do­main.tld.) Cross-do­main se­cur­ity policies re­quire that the fully-qual­i­fied do­mains match each oth­er ex­actly (which the ex­amples do not). Sub­do­mains, however, are al­lowed to be loosened to a com­mon gen­er­ic do­main through JavaS­cript’s doc­u­ment.do­main prop­erty. The ex­ample avail­able through the link at top right of this page has ht­tp://al­exaitken.net re­quest pages on ht­tp://dev.al­exaitken.net in to an iframe (with black bor­der), then res­izes the iframe to its con­tents. The items be­low are the key com­pon­ents of the solu­tion.

  1. JavaS­cript with­in the al­exaitken.net host page that sets doc­u­ment.do­main prop­erty to ‘al­exaitken.net’ (even though this is the host page’s do­main, the prop­erty needs to be set ex­pli­citly for com­mu­nic­a­tions.)
  2. JavaS­cript with­in each of the dev.al­exaitken.net pages that sets doc­u­ment.do­main prop­erty to ‘al­exaitken.net’ (a loosened ver­sion of their true do­main)
  3. Listen­ing from the host page for the iframe’s load event.
  4. On load, cal­cu­la­tion of the height of the iframe page (doc­u­ment.getEle­ments­By­Tag­Name(‘iframe’)[0].con­tentWin­dow.doc­u­ment.body) from the host page.
  5. Set­ting of the iframe ele­ment’s height to the height of its con­tained doc­u­ment.

As IE and Web­kit browsers ap­pear to in­tro­duce a race con­di­tion that makes the iframe page’s doc­u­ment.do­main prop­erty in­ac­cess­ible at the time the iframe fires its load event, an in­ter­val tries ac­cess­ing iframe page prop­er­ties un­til suc­cess­ful. The code for this first piece of the solu­tion looks like this:

  1: $(doc­u­ment).ready(func­tion(){ //for some reas­on this runs twice on click­ing through to full post without us­ing 'one'

  2:

  3:   //scoped vari­ables

  4:   var avail­ab­il­ity­In­ter­val, //setInt­er­val used to re-fire re­sume­Code func­tion un­til race con­di­tion dis­ap­pears (for IE (and web­kit?)) and doc­u­ment.do­main set­ting in child win­dow is re­cog­nized by par­ent. 

  5:     $child­Body, //iframe win­dow's doc­u­ment.body

  6:     iframe, //iframe

  7:     iframeOn­load,//iframe on­load func­tion

  8:     pre­Iframe­Height, //meas­ure of child­Body's off­setHeight be­fore iframe's height is re­set.

  9:     pos­tI­frame­Height, //meas­ure of child­Body's off­setHeight after iframe's height is re­set. 

 10:     res­izing­In­ter­val, //setInt­er­val used to re-fire setHeight func­tion un­til pre­Iframe­Height = pos­tI­frame­Height. used to ac­count for an­im­a­tions with­in child­Body.

 11:     re­sume­Code, //func­tion: fires un­til com­mu­nic­a­tion between par­ent and child win­dow is es­tab­lished and at that point re-sizes and shows iframe. Also binds the res­iz­ing func­tion to all fu­ture iframe page loads.  

 12:     se­tI­frame­Height; //func­tion: sets height of iframe ac­cord­ing to its child win­dow con­tent.

 13:

 14:   doc­u­ment.do­main='al­exaitken.net';

 15:

 16:   re­sume­Code=func­tion(){

 17:     try{

 18:       if(iframe.con­tentWin­dow.okToRe­sumePar­ent===true){

 19:         iframeOn­load();

 20:         clear­Int­er­val(avail­ab­il­ity­In­ter­val);

 21:       }

 22:     }catch(er­ror){};

 23:   };

 24:

 25:   se­tI­frame­Height=func­tion(){

 26:     pre­Iframe­Height=iframe.style.height;

 27:     iframe.style.height=$child­Body.height()+20+'px';//50 handles items get­ting cut off ver­tic­ally some­times...

 28:     pos­tI­frame­Height=iframe.style.height;

 29:     if(pre­Iframe­Height===pos­tI­frame­Height){clear­Int­er­val(res­izing­In­ter­val);}

 30:   };

 31:

 32:   iframeOn­load=func­tion(){

 33:     $child­Body=$(iframe.con­tentWin­dow.doc­u­ment.body);

 34:     se­tI­frame­Height();

 35:     $child­Body.bind('click',func­tion(){ //tests and res­izes child page as ne­ces­sary for each click with­in

 36:       res­izing­In­ter­val=setInt­er­val(se­tI­frame­Height,100);

 37:     });

 38:   };

 39:

 40:   //Be­ha­vi­or at­tach­ment lo­gic

 41:   iframe=$('iframe').get(0);//re­turns nat­ive dom ele­ment - at this point, we know it's been cre­ated and ad­ded

 42:   $('iframe').un­bind().bind('load',func­tion(){

 43:     avail­ab­il­ity­In­ter­val=setInt­er­val(re­sume­Code,50); //check 20 times/second for dis­ap­pear­ance of race con­di­tion (re­sum­Code clears check and kicks off the page init);      

 44:   });

 45:

 46: });

Dy­nam­ic Win­dows from Iframe Pages

One of the ap­plic­a­tions we framed with this solu­tion used JavaS­cript in some cases to gen­er­ate new win­dows with dy­nam­ic con­tent. Moz­illa and Web­kit du­ti­fully cre­ated and pop­u­lated the win­dows from the iframe page. But IE of course pulled its usu­al trick of tak­ing a dif­fer­ent tack.

What we found on this one was that cre­at­ing a new win­dow with a simple win­dow.open() state­ment ac­tu­ally de­faul­ted the IE win­dow loc­a­tion to about:blank, which the browser con­sidered a dif­fer­ent do­main. Sub­sequent at­tempts to write con­tent to the win­dow failed be­cause of cross-do­main is­sues.

Here the solu­tion was to place a dummy file empty of all con­tent ex­cept a doc­u­ment.do­main-set­ting JS snip­pet with­in the cur­rent do­main’s file sys­tem, re­quest that file as the URL of the new win­dow, and use doc­u­ment.write to pop­u­late it with the dy­nam­ic html con­tent. The one po­ten­tial gotcha is in the fact that un­less the new con­tent also in­cludes JS that sets doc­u­ment.do­main, the first doc­u­ment.write state­ment will work but fur­ther at­tempts to ac­cess doc­u­ment prop­er­ties and meth­ods (such as doc­u­ment.close) will fail. Once do­main is set in the dummy file and writ­ten in with new dy­nam­ic con­tent, even IE gives up the ghost. That code looks like this:

  1: var open­And­Write=func­tion(){

  2:   var win­Op­tions="status=no,tool­bar=no,loc­a­tion=no,menubar=no,dir­ect­or­ies=no,res­iz­able=no,scroll­bars=no,height=300,width=300",

  3:     w=win­dow.open('dummy.htm','re­ceiv­er',win­Op­tions),

  4:     in­ter­valOne,

  5:     in­ter­valT­wo;

  6:   in­ter­valOne=setInt­er­val(func­tion(){

  7:     try{

  8:       w.doc­u­ment.write(

  9:         '<html><head><title>Dy­nam­ic Win­dow</title>'+

 10:         '<link rel="stylesheet" type="text/css" href="/_system/css/core.css">'+

 11:         '<script type="text/javas­cript" src="doc­Do­main­Set­ter.js"><\/script>'+

 12:         '</head><body style="pad­ding:10px">'+

 13:         'This win­dow and con­tents writ­ten dy­nam­ic­ally via func­tion call from iframed page with loosened doc­u­ment.do­main prop­erty.'+

 14:         '</body></html>'

 15:       );

 16:       clear­Int­er­val(in­ter­valOne);

 17:       in­ter­valT­wo=setInt­er­val(func­tion(){

 18:         w.doc­u­ment.close();

 19:         clear­Int­er­val(in­ter­valOne);

 20:       },50)

 21:     }

 22:     catch(er­ror){}

 23:   },50)

 24: };

Ba­bies and Bath Wa­ter

Some sites I came across in re­search­ing this solu­tion main­tained flat-out that iframes have no place in mod­ern web de­vel­op­ment and should be avoided at all costs. In my situ­ation however, they proved the only op­tion cap­able of provid­ing the ex­per­i­ence we wanted to of­fer our cus­tom­ers. So I main­tain that while yes, they can be com­plic­ated to im­ple­ment, they have their time and place. On ar­riv­ing at these, we as com­pet­ent in­ter­face and ex­per­i­ence de­velopers should be will­ing to as­sume a de­gree of sys­tem com­plex­ity in or­der to make the lives of our users bet­ter.

§ One Response to “IFrame Solutions”

meta

Tags: , , , , ,