comparison iframe-resizer/src/iframeResizer.js @ 0:ac5f9272033b draft

first upload
author saskia-hiltemann
date Tue, 01 Jul 2014 11:42:23 -0400
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:ac5f9272033b
1 /*
2 * File: iframeReizer.js
3 * Desc: Force iframes to size to content.
4 * Requires: iframeResizer.contentWindow.js to be loaded into the target frame.
5 * Author: David J. Bradshaw - dave@bradshaw.net
6 * Contributor: Jure Mav - jure.mav@gmail.com
7 */
8 ;( function() {
9 'use strict';
10
11 var
12 count = 0,
13 firstRun = true,
14 msgHeader = 'message',
15 msgHeaderLen = msgHeader.length,
16 msgId = '[iFrameSizer]', //Must match iframe msg ID
17 msgIdLen = msgId.length,
18 page = '', //:'+location.href, //Uncoment to debug nested iFrames
19 pagePosition = null,
20 requestAnimationFrame = window.requestAnimationFrame,
21 resetRequiredMethods = {max:1,scroll:1,bodyScroll:1,documentElementScroll:1},
22 settings = {},
23
24 defaults = {
25 autoResize : true,
26 bodyBackground : null,
27 bodyMargin : null,
28 bodyMarginV1 : 8,
29 bodyPadding : null,
30 checkOrigin : true,
31 enablePublicMethods : false,
32 heightCalculationMethod : 'offset',
33 interval : 32,
34 log : false,
35 maxHeight : Infinity,
36 maxWidth : Infinity,
37 minHeight : 0,
38 minWidth : 0,
39 scrolling : false,
40 sizeHeight : true,
41 sizeWidth : false,
42 tolerance : 0,
43 closedCallback : function(){},
44 initCallback : function(){},
45 messageCallback : function(){},
46 resizedCallback : function(){}
47 };
48
49 function addEventListener(obj,evt,func){
50 if ('addEventListener' in window){
51 obj.addEventListener(evt,func, false);
52 } else if ('attachEvent' in window){//IE
53 obj.attachEvent('on'+evt,func);
54 }
55 }
56
57 function setupRequestAnimationFrame(){
58 var
59 vendors = ['moz', 'webkit', 'o', 'ms'],
60 x;
61
62 // Remove vendor prefixing if prefixed and break early if not
63 for (x = 0; x < vendors.length && !requestAnimationFrame; x += 1) {
64 requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
65 }
66
67 if (!(requestAnimationFrame)){
68 log(' RequestAnimationFrame not supported');
69 }
70 }
71
72 function log(msg){
73 if (settings.log && (typeof console === 'object')){
74 console.log(msgId + '[Host page'+page+']' + msg);
75 }
76 }
77
78
79 function iFrameListener(event){
80 function resizeIFrame(){
81 function resize(){
82 setSize(messageData);
83 setPagePosition();
84 settings.resizedCallback(messageData);
85 }
86
87 syncResize(resize,messageData,'resetPage');
88 }
89
90 function closeIFrame(iframe){
91 var iframeID = iframe.id;
92
93 log(' Removing iFrame: '+iframeID);
94 iframe.parentNode.removeChild(iframe);
95 settings.closedCallback(iframeID);
96 log(' --');
97 }
98
99 function processMsg(){
100 var data = msg.substr(msgIdLen).split(':');
101
102 return {
103 iframe: document.getElementById(data[0]),
104 id: data[0],
105 height: data[1],
106 width: data[2],
107 type: data[3]
108 };
109 }
110
111 function ensureInRange(Dimension){
112 var
113 max = Number(settings['max'+Dimension]),
114 min = Number(settings['min'+Dimension]),
115 dimension = Dimension.toLowerCase(),
116 size = Number(messageData[dimension]);
117
118 if (min>max){
119 throw new Error('Value for min'+Dimension+' can not be greater than max'+Dimension);
120 }
121
122 log(' Checking '+dimension+' is in range '+min+'-'+max);
123
124 if (size<min) {
125 size=min;
126 log(' Set '+dimension+' to min value');
127 }
128
129 if (size>max) {
130 size=max;
131 log(' Set '+dimension+' to max value');
132 }
133
134 messageData[dimension]=''+size;
135 }
136
137 function isMessageFromIFrame(){
138
139 var
140 origin = event.origin,
141 remoteHost = messageData.iframe.src.split('/').slice(0,3).join('/');
142
143 if (settings.checkOrigin) {
144 log(' Checking connection is from: '+remoteHost);
145
146 if ((''+origin !== 'null') && (origin !== remoteHost)) {
147 throw new Error(
148 'Unexpected message received from: ' + origin +
149 ' for ' + messageData.iframe.id +
150 '. Message was: ' + event.data +
151 '. This error can be disabled by adding the checkOrigin: false option.'
152 );
153 }
154 }
155
156 return true;
157 }
158
159 function isMessageForUs(){
160 return msgId === ('' + msg).substr(0,msgIdLen); //''+Protects against non-string msg
161 }
162
163 function isMessageFromMetaParent(){
164 //test if this message is from a parent above us. This is an ugly test, however, updating
165 //the message format would break backwards compatibity.
166 var retCode = messageData.type in {'true':1,'false':1};
167
168 if (retCode){
169 log(' Ignoring init message from meta parent page');
170 }
171
172 return retCode;
173 }
174
175 function forwardMsgFromIFrame(){
176 var msgBody = msg.substr(msg.indexOf(':')+msgHeaderLen+6); //6 === ':0:0:' + ':' (Ideas to name this magic number most welcome)
177
178 log(' MessageCallback passed: {iframe: '+ messageData.iframe.id + ', message: ' + msgBody + '}');
179 settings.messageCallback({
180 iframe: messageData.iframe,
181 message: msgBody
182 });
183 log(' --');
184 }
185
186 function checkIFrameExists(){
187 if (null === messageData.iframe) {
188 throw new Error('iFrame ('+messageData.id+') does not exist on ' + page);
189 }
190 return true;
191 }
192
193 function actionMsg(){
194 switch(messageData.type){
195 case 'close':
196 closeIFrame(messageData.iframe);
197 settings.resizedCallback(messageData); //To be removed.
198 break;
199 case 'message':
200 forwardMsgFromIFrame();
201 break;
202 case 'reset':
203 resetIFrame(messageData);
204 break;
205 case 'init':
206 resizeIFrame();
207 settings.initCallback(messageData.iframe);
208 break;
209 default:
210 resizeIFrame();
211 }
212 }
213
214 var
215 msg = event.data,
216 messageData = {};
217
218 if (isMessageForUs()){
219 log(' Received: '+msg);
220 messageData = processMsg();
221 ensureInRange('Height');
222 ensureInRange('Width');
223
224 if ( !isMessageFromMetaParent() && checkIFrameExists() && isMessageFromIFrame() ){
225 actionMsg();
226 firstRun = false;
227 }
228 }
229 }
230
231
232 function getPagePosition (){
233 if(null === pagePosition){
234 pagePosition = {
235 x: (window.pageXOffset !== undefined) ? window.pageXOffset : document.documentElement.scrollLeft,
236 y: (window.pageYOffset !== undefined) ? window.pageYOffset : document.documentElement.scrollTop
237 };
238 log(' Get position: '+pagePosition.x+','+pagePosition.y);
239 }
240 }
241
242 function setPagePosition(){
243 if(null !== pagePosition){
244 window.scrollTo(pagePosition.x,pagePosition.y);
245 log(' Set position: '+pagePosition.x+','+pagePosition.y);
246 pagePosition = null;
247 }
248 }
249
250 function resetIFrame(messageData){
251 function reset(){
252 setSize(messageData);
253 trigger('reset','reset',messageData.iframe);
254 }
255
256 log(' Size reset requested by '+('init'===messageData.type?'host page':'iFrame'));
257 getPagePosition();
258 syncResize(reset,messageData,'init');
259 }
260
261 function setSize(messageData){
262 function setDimension(dimension,min,max){
263 messageData.iframe.style[dimension] = messageData[dimension] + 'px';
264 log(
265 ' IFrame (' + messageData.iframe.id +
266 ') ' + dimension +
267 ' set to ' + messageData[dimension] + 'px'
268 );
269 }
270
271 if( settings.sizeHeight) { setDimension('height'); }
272 if( settings.sizeWidth ) { setDimension('width'); }
273 }
274
275 function syncResize(func,messageData,doNotSync){
276 if(doNotSync!==messageData.type && requestAnimationFrame){
277 log(' Requesting animation frame');
278 requestAnimationFrame(func);
279 } else {
280 func();
281 }
282 }
283
284 function trigger(calleeMsg,msg,iframe){
285 log('[' + calleeMsg + '] Sending msg to iframe ('+msg+')');
286 iframe.contentWindow.postMessage( msgId + msg, '*' );
287 }
288
289
290 function setupIFrame(){
291 function setLimits(){
292 function addStyle(style){
293 if ((Infinity !== settings[style]) && (0 !== settings[style])){
294 iframe.style[style] = settings[style] + 'px';
295 log(' Set '+style+' = '+settings[style]+'px');
296 }
297 }
298
299 addStyle('maxHeight');
300 addStyle('minHeight');
301 addStyle('maxWidth');
302 addStyle('minWidth');
303 }
304
305 function ensureHasId(iframeID){
306 if (''===iframeID){
307 iframe.id = iframeID = 'iFrameResizer' + count++;
308 log(' Added missing iframe ID: '+ iframeID);
309 }
310
311 return iframeID;
312 }
313
314 function setScrolling(){
315 log(' IFrame scrolling ' + (settings.scrolling ? 'enabled' : 'disabled') + ' for ' + iframeID);
316 iframe.style.overflow = false === settings.scrolling ? 'hidden' : 'auto';
317 iframe.scrolling = false === settings.scrolling ? 'no' : 'yes';
318 }
319
320 //The V1 iFrame script expects an int, where as in V2 expects a CSS
321 //string value such as '1px 3em', so if we have an int for V2, set V1=V2
322 //and then convert V2 to a string PX value.
323 function setupBodyMarginValues(){
324 if (('number'===typeof(settings.bodyMargin)) || ('0'===settings.bodyMargin)){
325 settings.bodyMarginV1 = settings.bodyMargin;
326 settings.bodyMargin = '' + settings.bodyMargin + 'px';
327 }
328 }
329
330 function createOutgoingMsg(){
331 return iframeID +
332 ':' + settings.bodyMarginV1 +
333 ':' + settings.sizeWidth +
334 ':' + settings.log +
335 ':' + settings.interval +
336 ':' + settings.enablePublicMethods +
337 ':' + settings.autoResize +
338 ':' + settings.bodyMargin +
339 ':' + settings.heightCalculationMethod +
340 ':' + settings.bodyBackground +
341 ':' + settings.bodyPadding +
342 ':' + settings.tolerance;
343 }
344
345 function init(msg){
346 //We have to call trigger twice, as we can not be sure if all
347 //iframes have completed loading when this code runs. The
348 //event listener also catches the page changing in the iFrame.
349 addEventListener(iframe,'load',function(){
350 var fr = firstRun; // Reduce scope of var to function, because IE8's JS execution
351 // context stack is borked and this value gets externally
352 // changed midway through running this function.
353 trigger('iFrame.onload',msg,iframe);
354 if (!fr && settings.heightCalculationMethod in resetRequiredMethods){
355 resetIFrame({
356 iframe:iframe,
357 height:0,
358 width:0,
359 type:'init'
360 });
361 }
362 });
363 trigger('init',msg,iframe);
364 }
365
366 var
367 /*jshint validthis:true */
368 iframe = this,
369 iframeID = ensureHasId(iframe.id);
370
371 setScrolling();
372 setLimits();
373 setupBodyMarginValues();
374 init(createOutgoingMsg());
375 }
376
377 function checkOptions(options){
378 if ('object' !== typeof options){
379 throw new TypeError('Options is not an object.');
380 }
381 }
382
383 function createNativePublicFunction(){
384 function init(element){
385 if('IFRAME' !== element.tagName) {
386 throw new TypeError('Expected <IFRAME> tag, found <'+element.tagName+'>.');
387 } else {
388 setupIFrame.call(element);
389 }
390 }
391
392 function processOptions(options){
393 options = options || {};
394
395 checkOptions(options);
396
397 for (var option in defaults) {
398 if (defaults.hasOwnProperty(option)){
399 settings[option] = options.hasOwnProperty(option) ? options[option] : defaults[option];
400 }
401 }
402 }
403
404 return function iFrameResizeF(options,selecter){
405 processOptions(options);
406 Array.prototype.forEach.call( document.querySelectorAll( selecter || 'iframe' ), init );
407 };
408 }
409
410 function createJQueryPublicMethod($){
411 $.fn.iFrameResize = function $iFrameResizeF(options) {
412 checkOptions(options);
413 settings = $.extend( {}, defaults, options );
414 return this.filter('iframe').each( setupIFrame ).end();
415 };
416 }
417
418 setupRequestAnimationFrame();
419 addEventListener(window,'message',iFrameListener);
420
421 if ('jQuery' in window) { createJQueryPublicMethod(jQuery); }
422
423 if (typeof define === 'function' && define.amd) {
424 define(function (){ return createNativePublicFunction(); });
425 } else {
426 window.iFrameResize = createNativePublicFunction();
427 }
428
429 })();