Mercurial > repos > saskia-hiltemann > ireport
diff iframe-resizer/src/iframeResizer.js @ 0:ac5f9272033b draft
first upload
author | saskia-hiltemann |
---|---|
date | Tue, 01 Jul 2014 11:42:23 -0400 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iframe-resizer/src/iframeResizer.js Tue Jul 01 11:42:23 2014 -0400 @@ -0,0 +1,429 @@ +/* + * File: iframeReizer.js + * Desc: Force iframes to size to content. + * Requires: iframeResizer.contentWindow.js to be loaded into the target frame. + * Author: David J. Bradshaw - dave@bradshaw.net + * Contributor: Jure Mav - jure.mav@gmail.com + */ +;( function() { + 'use strict'; + + var + count = 0, + firstRun = true, + msgHeader = 'message', + msgHeaderLen = msgHeader.length, + msgId = '[iFrameSizer]', //Must match iframe msg ID + msgIdLen = msgId.length, + page = '', //:'+location.href, //Uncoment to debug nested iFrames + pagePosition = null, + requestAnimationFrame = window.requestAnimationFrame, + resetRequiredMethods = {max:1,scroll:1,bodyScroll:1,documentElementScroll:1}, + settings = {}, + + defaults = { + autoResize : true, + bodyBackground : null, + bodyMargin : null, + bodyMarginV1 : 8, + bodyPadding : null, + checkOrigin : true, + enablePublicMethods : false, + heightCalculationMethod : 'offset', + interval : 32, + log : false, + maxHeight : Infinity, + maxWidth : Infinity, + minHeight : 0, + minWidth : 0, + scrolling : false, + sizeHeight : true, + sizeWidth : false, + tolerance : 0, + closedCallback : function(){}, + initCallback : function(){}, + messageCallback : function(){}, + resizedCallback : function(){} + }; + + function addEventListener(obj,evt,func){ + if ('addEventListener' in window){ + obj.addEventListener(evt,func, false); + } else if ('attachEvent' in window){//IE + obj.attachEvent('on'+evt,func); + } + } + + function setupRequestAnimationFrame(){ + var + vendors = ['moz', 'webkit', 'o', 'ms'], + x; + + // Remove vendor prefixing if prefixed and break early if not + for (x = 0; x < vendors.length && !requestAnimationFrame; x += 1) { + requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame']; + } + + if (!(requestAnimationFrame)){ + log(' RequestAnimationFrame not supported'); + } + } + + function log(msg){ + if (settings.log && (typeof console === 'object')){ + console.log(msgId + '[Host page'+page+']' + msg); + } + } + + + function iFrameListener(event){ + function resizeIFrame(){ + function resize(){ + setSize(messageData); + setPagePosition(); + settings.resizedCallback(messageData); + } + + syncResize(resize,messageData,'resetPage'); + } + + function closeIFrame(iframe){ + var iframeID = iframe.id; + + log(' Removing iFrame: '+iframeID); + iframe.parentNode.removeChild(iframe); + settings.closedCallback(iframeID); + log(' --'); + } + + function processMsg(){ + var data = msg.substr(msgIdLen).split(':'); + + return { + iframe: document.getElementById(data[0]), + id: data[0], + height: data[1], + width: data[2], + type: data[3] + }; + } + + function ensureInRange(Dimension){ + var + max = Number(settings['max'+Dimension]), + min = Number(settings['min'+Dimension]), + dimension = Dimension.toLowerCase(), + size = Number(messageData[dimension]); + + if (min>max){ + throw new Error('Value for min'+Dimension+' can not be greater than max'+Dimension); + } + + log(' Checking '+dimension+' is in range '+min+'-'+max); + + if (size<min) { + size=min; + log(' Set '+dimension+' to min value'); + } + + if (size>max) { + size=max; + log(' Set '+dimension+' to max value'); + } + + messageData[dimension]=''+size; + } + + function isMessageFromIFrame(){ + + var + origin = event.origin, + remoteHost = messageData.iframe.src.split('/').slice(0,3).join('/'); + + if (settings.checkOrigin) { + log(' Checking connection is from: '+remoteHost); + + if ((''+origin !== 'null') && (origin !== remoteHost)) { + throw new Error( + 'Unexpected message received from: ' + origin + + ' for ' + messageData.iframe.id + + '. Message was: ' + event.data + + '. This error can be disabled by adding the checkOrigin: false option.' + ); + } + } + + return true; + } + + function isMessageForUs(){ + return msgId === ('' + msg).substr(0,msgIdLen); //''+Protects against non-string msg + } + + function isMessageFromMetaParent(){ + //test if this message is from a parent above us. This is an ugly test, however, updating + //the message format would break backwards compatibity. + var retCode = messageData.type in {'true':1,'false':1}; + + if (retCode){ + log(' Ignoring init message from meta parent page'); + } + + return retCode; + } + + function forwardMsgFromIFrame(){ + var msgBody = msg.substr(msg.indexOf(':')+msgHeaderLen+6); //6 === ':0:0:' + ':' (Ideas to name this magic number most welcome) + + log(' MessageCallback passed: {iframe: '+ messageData.iframe.id + ', message: ' + msgBody + '}'); + settings.messageCallback({ + iframe: messageData.iframe, + message: msgBody + }); + log(' --'); + } + + function checkIFrameExists(){ + if (null === messageData.iframe) { + throw new Error('iFrame ('+messageData.id+') does not exist on ' + page); + } + return true; + } + + function actionMsg(){ + switch(messageData.type){ + case 'close': + closeIFrame(messageData.iframe); + settings.resizedCallback(messageData); //To be removed. + break; + case 'message': + forwardMsgFromIFrame(); + break; + case 'reset': + resetIFrame(messageData); + break; + case 'init': + resizeIFrame(); + settings.initCallback(messageData.iframe); + break; + default: + resizeIFrame(); + } + } + + var + msg = event.data, + messageData = {}; + + if (isMessageForUs()){ + log(' Received: '+msg); + messageData = processMsg(); + ensureInRange('Height'); + ensureInRange('Width'); + + if ( !isMessageFromMetaParent() && checkIFrameExists() && isMessageFromIFrame() ){ + actionMsg(); + firstRun = false; + } + } + } + + + function getPagePosition (){ + if(null === pagePosition){ + pagePosition = { + x: (window.pageXOffset !== undefined) ? window.pageXOffset : document.documentElement.scrollLeft, + y: (window.pageYOffset !== undefined) ? window.pageYOffset : document.documentElement.scrollTop + }; + log(' Get position: '+pagePosition.x+','+pagePosition.y); + } + } + + function setPagePosition(){ + if(null !== pagePosition){ + window.scrollTo(pagePosition.x,pagePosition.y); + log(' Set position: '+pagePosition.x+','+pagePosition.y); + pagePosition = null; + } + } + + function resetIFrame(messageData){ + function reset(){ + setSize(messageData); + trigger('reset','reset',messageData.iframe); + } + + log(' Size reset requested by '+('init'===messageData.type?'host page':'iFrame')); + getPagePosition(); + syncResize(reset,messageData,'init'); + } + + function setSize(messageData){ + function setDimension(dimension,min,max){ + messageData.iframe.style[dimension] = messageData[dimension] + 'px'; + log( + ' IFrame (' + messageData.iframe.id + + ') ' + dimension + + ' set to ' + messageData[dimension] + 'px' + ); + } + + if( settings.sizeHeight) { setDimension('height'); } + if( settings.sizeWidth ) { setDimension('width'); } + } + + function syncResize(func,messageData,doNotSync){ + if(doNotSync!==messageData.type && requestAnimationFrame){ + log(' Requesting animation frame'); + requestAnimationFrame(func); + } else { + func(); + } + } + + function trigger(calleeMsg,msg,iframe){ + log('[' + calleeMsg + '] Sending msg to iframe ('+msg+')'); + iframe.contentWindow.postMessage( msgId + msg, '*' ); + } + + + function setupIFrame(){ + function setLimits(){ + function addStyle(style){ + if ((Infinity !== settings[style]) && (0 !== settings[style])){ + iframe.style[style] = settings[style] + 'px'; + log(' Set '+style+' = '+settings[style]+'px'); + } + } + + addStyle('maxHeight'); + addStyle('minHeight'); + addStyle('maxWidth'); + addStyle('minWidth'); + } + + function ensureHasId(iframeID){ + if (''===iframeID){ + iframe.id = iframeID = 'iFrameResizer' + count++; + log(' Added missing iframe ID: '+ iframeID); + } + + return iframeID; + } + + function setScrolling(){ + log(' IFrame scrolling ' + (settings.scrolling ? 'enabled' : 'disabled') + ' for ' + iframeID); + iframe.style.overflow = false === settings.scrolling ? 'hidden' : 'auto'; + iframe.scrolling = false === settings.scrolling ? 'no' : 'yes'; + } + + //The V1 iFrame script expects an int, where as in V2 expects a CSS + //string value such as '1px 3em', so if we have an int for V2, set V1=V2 + //and then convert V2 to a string PX value. + function setupBodyMarginValues(){ + if (('number'===typeof(settings.bodyMargin)) || ('0'===settings.bodyMargin)){ + settings.bodyMarginV1 = settings.bodyMargin; + settings.bodyMargin = '' + settings.bodyMargin + 'px'; + } + } + + function createOutgoingMsg(){ + return iframeID + + ':' + settings.bodyMarginV1 + + ':' + settings.sizeWidth + + ':' + settings.log + + ':' + settings.interval + + ':' + settings.enablePublicMethods + + ':' + settings.autoResize + + ':' + settings.bodyMargin + + ':' + settings.heightCalculationMethod + + ':' + settings.bodyBackground + + ':' + settings.bodyPadding + + ':' + settings.tolerance; + } + + function init(msg){ + //We have to call trigger twice, as we can not be sure if all + //iframes have completed loading when this code runs. The + //event listener also catches the page changing in the iFrame. + addEventListener(iframe,'load',function(){ + var fr = firstRun; // Reduce scope of var to function, because IE8's JS execution + // context stack is borked and this value gets externally + // changed midway through running this function. + trigger('iFrame.onload',msg,iframe); + if (!fr && settings.heightCalculationMethod in resetRequiredMethods){ + resetIFrame({ + iframe:iframe, + height:0, + width:0, + type:'init' + }); + } + }); + trigger('init',msg,iframe); + } + + var + /*jshint validthis:true */ + iframe = this, + iframeID = ensureHasId(iframe.id); + + setScrolling(); + setLimits(); + setupBodyMarginValues(); + init(createOutgoingMsg()); + } + + function checkOptions(options){ + if ('object' !== typeof options){ + throw new TypeError('Options is not an object.'); + } + } + + function createNativePublicFunction(){ + function init(element){ + if('IFRAME' !== element.tagName) { + throw new TypeError('Expected <IFRAME> tag, found <'+element.tagName+'>.'); + } else { + setupIFrame.call(element); + } + } + + function processOptions(options){ + options = options || {}; + + checkOptions(options); + + for (var option in defaults) { + if (defaults.hasOwnProperty(option)){ + settings[option] = options.hasOwnProperty(option) ? options[option] : defaults[option]; + } + } + } + + return function iFrameResizeF(options,selecter){ + processOptions(options); + Array.prototype.forEach.call( document.querySelectorAll( selecter || 'iframe' ), init ); + }; + } + + function createJQueryPublicMethod($){ + $.fn.iFrameResize = function $iFrameResizeF(options) { + checkOptions(options); + settings = $.extend( {}, defaults, options ); + return this.filter('iframe').each( setupIFrame ).end(); + }; + } + + setupRequestAnimationFrame(); + addEventListener(window,'message',iFrameListener); + + if ('jQuery' in window) { createJQueryPublicMethod(jQuery); } + + if (typeof define === 'function' && define.amd) { + define(function (){ return createNativePublicFunction(); }); + } else { + window.iFrameResize = createNativePublicFunction(); + } + +})();