Mercurial > repos > saskia-hiltemann > ireport
diff iframe-resizer/src/iframeResizer.contentWindow.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.contentWindow.js Tue Jul 01 11:42:23 2014 -0400 @@ -0,0 +1,564 @@ +/* + * File: iframeSizer.contentWindow.js + * Desc: Include this file in any page being loaded into an iframe + * to force the iframe to resize to the content size. + * Requires: iframeResizer.js on host page. + * Author: David J. Bradshaw - dave@bradshaw.net + * Contributor: Jure Mav - jure.mav@gmail.com + */ + +;(function() { + 'use strict'; + + var + autoResize = true, + base = 10, + bodyBackground = '', + bodyMargin = 0, + bodyMarginStr = '', + bodyPadding = '', + calculateWidth = false, + doubleEventList = {'resize':1,'click':1}, + eventCancelTimer = 64, + height = 1, + firstRun = true, + heightCalcModeDefault = 'offset', + heightCalcMode = heightCalcModeDefault, + initLock = true, + initMsg = '', + interval = 32, + logging = false, + msgID = '[iFrameSizer]', //Must match host page msg ID + msgIdLen = msgID.length, + myID = '', + publicMethods = false, + resetRequiredMethods = {max:1,scroll:1,bodyScroll:1,documentElementScroll:1}, + targetOriginDefault = '*', + target = window.parent, + tolerance = 0, + triggerLocked = false, + triggerLockedTimer = null, + width = 1; + + + function addEventListener(el,evt,func){ + if ('addEventListener' in window){ + el.addEventListener(evt,func, false); + } else if ('attachEvent' in window){ //IE + el.attachEvent('on'+evt,func); + } + } + + function formatLogMsg(msg){ + return msgID + '[' + myID + ']' + ' ' + msg; + } + + function log(msg){ + if (logging && ('object' === typeof window.console)){ + console.log(formatLogMsg(msg)); + } + } + + function warn(msg){ + if ('object' === typeof window.console){ + console.warn(formatLogMsg(msg)); + } + } + + + function init(){ + log('Initialising iFrame'); + readData(); + setMargin(); + setBodyStyle('background',bodyBackground); + setBodyStyle('padding',bodyPadding); + injectClearFixIntoBodyElement(); + checkHeightMode(); + stopInfiniteResizingOfIFrame(); + setupPublicMethods(); + startEventListeners(); + sendSize('init','Init message from host page'); + } + + function readData(){ + + var data = initMsg.substr(msgIdLen).split(':'); + + function strBool(str){ + return 'true' === str ? true : false; + } + + myID = data[0]; + bodyMargin = (undefined !== data[1]) ? Number(data[1]) : bodyMargin; //For V1 compatibility + calculateWidth = (undefined !== data[2]) ? strBool(data[2]) : calculateWidth; + logging = (undefined !== data[3]) ? strBool(data[3]) : logging; + interval = (undefined !== data[4]) ? Number(data[4]) : interval; + publicMethods = (undefined !== data[5]) ? strBool(data[5]) : publicMethods; + autoResize = (undefined !== data[6]) ? strBool(data[6]) : autoResize; + bodyMarginStr = data[7]; + heightCalcMode = (undefined !== data[8]) ? data[8] : heightCalcMode; + bodyBackground = data[9]; + bodyPadding = data[10]; + tolerance = (undefined !== data[11]) ? Number(data[11]) : tolerance; + } + + function chkCSS(attr,value){ + if (-1 !== value.indexOf('-')){ + warn('Negative CSS value ignored for '+attr); + value=''; + } + return value; + } + + function setBodyStyle(attr,value){ + if ((undefined !== value) && ('' !== value) && ('null' !== value)){ + document.body.style[attr] = value; + log('Body '+attr+' set to "'+value+'"'); + } + } + + function setMargin(){ + //If called via V1 script, convert bodyMargin from int to str + if (undefined === bodyMarginStr){ + bodyMarginStr = bodyMargin+'px'; + } + chkCSS('margin',bodyMarginStr); + setBodyStyle('margin',bodyMarginStr); + } + + function stopInfiniteResizingOfIFrame(){ + document.documentElement.style.height = ''; + document.body.style.height = ''; + log('HTML & body height set to "auto"'); + } + + function initWindowResizeListener(){ + addEventListener(window,'resize', function(){ + sendSize('resize','Window resized'); + }); + } + + function initWindowClickListener(){ + addEventListener(window,'click', function(){ + sendSize('click','Window clicked'); + }); + } + + function checkHeightMode(){ + if (heightCalcModeDefault !== heightCalcMode){ + if (!(heightCalcMode in getHeight)){ + warn(heightCalcMode + ' is not a valid option for heightCalculationMethod.'); + heightCalcMode='bodyScroll'; + } + log('Height calculation method set to "'+heightCalcMode+'"'); + } + } + + function startEventListeners(){ + if ( true === autoResize ) { + initWindowResizeListener(); + initWindowClickListener(); + setupMutationObserver(); + } + else { + log('Auto Resize disabled'); + } + } + + function injectClearFixIntoBodyElement(){ + var clearFix = document.createElement('div'); + clearFix.style.clear = 'both'; + clearFix.style.display = 'block'; //Guard against this having been globally redefined in CSS. + document.body.appendChild(clearFix); + } + + function setupPublicMethods(){ + if (publicMethods) { + log('Enable public methods'); + + window.parentIFrame = { + close: function closeF(){ + sendSize('close','parentIFrame.close()', 0, 0); + }, + getId: function getIdF(){ + return myID; + }, + reset: function resetF(){ + resetIFrame('parentIFrame.size'); + }, + sendMessage: function sendMessageF(msg,targetOrigin){ + sendMsg(0,0,'message',msg,targetOrigin); + }, + setHeightCalculationMethod: function setHeightCalculationMethodF(heightCalculationMethod){ + heightCalcMode = heightCalculationMethod; + checkHeightMode(); + }, + setTargetOrigin: function setTargetOriginF(targetOrigin){ + log('Set targetOrigin: '+targetOrigin); + targetOriginDefault = targetOrigin; + }, + size: function sizeF(customHeight, customWidth){ + var valString = ''+(customHeight?customHeight:'')+(customWidth?','+customWidth:''); + lockTrigger(); + sendSize('size','parentIFrame.size('+valString+')', customHeight, customWidth); + } + }; + } + } + + function initInterval(){ + if ( 0 !== interval ){ + log('setInterval: '+interval+'ms'); + setInterval(function(){ + sendSize('interval','setInterval: '+interval); + },Math.abs(interval)); + } + } + + function setupInjectElementLoadListners(mutations){ + function addLoadListener(element){ + if (element.height === undefined || element.width === undefined || 0 === element.height || 0 === element.width){ + log('Attach listerner to '+element.src); + addEventListener(element,'load', function imageLoaded(){ + sendSize('imageLoad','Image loaded'); + }); + } + } + + mutations.forEach(function (mutation) { + if (mutation.type === 'attributes' && mutation.attributeName === 'src'){ + addLoadListener(mutation.target); + } else if (mutation.type === 'childList'){ + var images = mutation.target.querySelectorAll('img'); + Array.prototype.forEach.call(images,function (image) { + addLoadListener(image); + }); + } + }); + } + + function setupMutationObserver(){ + + var MutationObserver = window.MutationObserver || window.WebKitMutationObserver; + + function createMutationObserver(){ + var + target = document.querySelector('body'), + + config = { + attributes : true, + attributeOldValue : false, + characterData : true, + characterDataOldValue : false, + childList : true, + subtree : true + }, + + observer = new MutationObserver(function(mutations) { + sendSize('mutationObserver','mutationObserver: ' + mutations[0].target + ' ' + mutations[0].type); + setupInjectElementLoadListners(mutations); //Deal with WebKit asyncing image loading when tags are injected into the page + }); + + log('Enable MutationObserver'); + observer.observe(target, config); + } + + if (MutationObserver){ + if (0 > interval) { + initInterval(); + } else { + createMutationObserver(); + } + } + else { + warn('MutationObserver not supported in this browser!'); + initInterval(); + } + } + + + // document.documentElement.offsetHeight is not reliable, so + // we have to jump through hoops to get a better value. + function getBodyOffsetHeight(){ + function getComputedBodyStyle(prop) { + function convertUnitsToPxForIE8(value) { + var PIXEL = /^\d+(px)?$/i; + + if (PIXEL.test(value)) { + return parseInt(value,base); + } + + var + style = el.style.left, + runtimeStyle = el.runtimeStyle.left; + + el.runtimeStyle.left = el.currentStyle.left; + el.style.left = value || 0; + value = el.style.pixelLeft; + el.style.left = style; + el.runtimeStyle.left = runtimeStyle; + + return value; + } + + var + el = document.body, + retVal = 0; + + if (('defaultView' in document) && ('getComputedStyle' in document.defaultView)) { + retVal = document.defaultView.getComputedStyle(el, null); + retVal = (null !== retVal) ? retVal[prop] : 0; + } else {//IE8 + retVal = convertUnitsToPxForIE8(el.currentStyle[prop]); + } + + return parseInt(retVal,base); + } + + return document.body.offsetHeight + + getComputedBodyStyle('marginTop') + + getComputedBodyStyle('marginBottom'); + } + + function getBodyScrollHeight(){ + return document.body.scrollHeight; + } + + function getDEOffsetHeight(){ + return document.documentElement.offsetHeight; + } + + function getDEScrollHeight(){ + return document.documentElement.scrollHeight; + } + + //From https://github.com/guardian/iframe-messenger + function getLowestElementHeight() { + var + allElements = document.querySelectorAll('body *'), + allElementsLength = allElements.length, + maxBottomVal = 0, + timer = new Date().getTime(); + + for (var i = 0; i < allElementsLength; i++) { + if (allElements[i].getBoundingClientRect().bottom > maxBottomVal) { + maxBottomVal = allElements[i].getBoundingClientRect().bottom; + } + } + + timer = new Date().getTime() - timer; + + log('Parsed '+allElementsLength+' HTML elements'); + log('LowestElement bottom position calculated in ' + timer + 'ms'); + + return maxBottomVal; + } + + function getAllHeights(){ + return [ + getBodyOffsetHeight(), + getBodyScrollHeight(), + getDEOffsetHeight(), + getDEScrollHeight() + ]; + } + + function getMaxHeight(){ + return Math.max.apply(null,getAllHeights()); + } + + function getMinHeight(){ + return Math.min.apply(null,getAllHeights()); + } + + function getBestHeight(){ + return Math.max(getBodyOffsetHeight(),getLowestElementHeight()); + } + + var getHeight = { + offset : getBodyOffsetHeight, //Backward compatability + bodyOffset : getBodyOffsetHeight, + bodyScroll : getBodyScrollHeight, + documentElementOffset : getDEOffsetHeight, + scroll : getDEScrollHeight, //Backward compatability + documentElementScroll : getDEScrollHeight, + max : getMaxHeight, + min : getMinHeight, + grow : getMaxHeight, + lowestElement : getBestHeight, + }; + + function getWidth(){ + return Math.max( + document.documentElement.scrollWidth, + document.body.scrollWidth + ); + } + + function sendSize(triggerEvent, triggerEventDesc, customHeight, customWidth){ + + var currentHeight,currentWidth; + + function recordTrigger(){ + if (!(triggerEvent in {'reset':1,'resetPage':1,'init':1})){ + log( 'Trigger event: ' + triggerEventDesc ); + } + } + + function resizeIFrame(){ + height = currentHeight; + width = currentWidth; + + sendMsg(height,width,triggerEvent); + } + + function isDoubleFiredEvent(){ + return triggerLocked && (triggerEvent in doubleEventList); + } + + function isSizeChangeDetected(){ + function checkTolarance(a,b){ + var retVal = Math.abs(a-b) <= tolerance; + return !retVal; + } + + currentHeight = (undefined !== customHeight) ? customHeight : getHeight[heightCalcMode](); + currentWidth = (undefined !== customWidth ) ? customWidth : getWidth(); + + return checkTolarance(height,currentHeight) || + (calculateWidth && checkTolarance(width,currentWidth)); + + //return (height !== currentHeight) || + // (calculateWidth && width !== currentWidth); + } + + function isForceResizableEvent(){ + return !(triggerEvent in {'init':1,'interval':1,'size':1}); + } + + function isForceResizableHeightCalcMode(){ + return (heightCalcMode in resetRequiredMethods); + } + + function logIgnored(){ + log('No change in size detected'); + } + + function checkDownSizing(){ + if (isForceResizableEvent() && isForceResizableHeightCalcMode()){ + resetIFrame(triggerEventDesc); + } else if (!(triggerEvent in {'interval':1})){ + recordTrigger(); + logIgnored(); + } + } + + if (!isDoubleFiredEvent()){ + if (isSizeChangeDetected()){ + recordTrigger(); + lockTrigger(); + resizeIFrame(); + } else { + checkDownSizing(); + } + } else { + log('Trigger event cancelled: '+triggerEvent); + } + } + + function lockTrigger(){ + if (!triggerLocked){ + triggerLocked = true; + log('Trigger event lock on'); + } + clearTimeout(triggerLockedTimer); + triggerLockedTimer = setTimeout(function(){ + triggerLocked = false; + log('Trigger event lock off'); + log('--'); + },eventCancelTimer); + } + + function triggerReset(triggerEvent){ + height = getHeight[heightCalcMode](); + width = getWidth(); + + sendMsg(height,width,triggerEvent); + } + + function resetIFrame(triggerEventDesc){ + var hcm = heightCalcMode; + heightCalcMode = heightCalcModeDefault; + + log('Reset trigger event: ' + triggerEventDesc); + lockTrigger(); + triggerReset('reset'); + + heightCalcMode = hcm; + } + + function sendMsg(height,width,triggerEvent,msg,targetOrigin){ + function setTargetOrigin(){ + if (undefined === targetOrigin){ + targetOrigin = targetOriginDefault; + } else { + log('Message targetOrigin: '+targetOrigin); + } + } + + function sendToParent(){ + var + size = height + ':' + width, + message = myID + ':' + size + ':' + triggerEvent + (undefined !== msg ? ':' + msg : ''); + + log('Sending message to host page (' + message + ')'); + target.postMessage( msgID + message, targetOrigin); + } + + setTargetOrigin(); + sendToParent(); + } + + function receiver(event) { + function isMessageForUs(){ + return msgID === (''+event.data).substr(0,msgIdLen); //''+ Protects against non-string messages + } + + function initFromParent(){ + initMsg = event.data; + init(); + firstRun = false; + setTimeout(function(){ initLock = false;},eventCancelTimer); + } + + function resetFromParent(){ + if (!initLock){ + log('Page size reset by host page'); + triggerReset('resetPage'); + } else { + log('Page reset ignored by init'); + } + } + + function getMessageType(){ + return event.data.split(']')[1]; + } + + function isMiddleTier(){ + return ('iFrameResize' in window); + } + + if (isMessageForUs()){ + if (firstRun){ //Check msg ID + initFromParent(); + } else if ('reset' === getMessageType()){ + resetFromParent(); + } else if (event.data !== initMsg && !isMiddleTier()){ + warn('Unexpected message ('+event.data+')'); + } + } + } + + addEventListener(window, 'message', receiver); + +})();