| 0 | 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 })(); |