|
|
/* * File: iframeResizer.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 * Contributor: Ian Caunce - ian@hallnet.co.uk */
;(function() { 'use strict';
var autoResize = true, base = 10, bodyBackground = '', bodyMargin = 0, bodyMarginStr = '', bodyPadding = '', calculateWidth = false, doubleEventList = {'resize':1,'click':1}, eventCancelTimer = 128, height = 1, firstRun = true, heightCalcModeDefault = 'offset', heightCalcMode = heightCalcModeDefault, initLock = true, initMsg = '', inPageLinks = {}, 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}, resizeFrom = 'parent', 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('Initializing iFrame'); readData(); setMargin(); setBodyStyle('background',bodyBackground); setBodyStyle('padding',bodyPadding); injectClearFixIntoBodyElement(); checkHeightMode(); stopInfiniteResizingOfIFrame(); setupPublicMethods(); startEventListeners(); inPageLinks = setupInPageLinks(); 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; inPageLinks.enable = (undefined !== data[12]) ? strBool(data[12]): false; resizeFrom = data[13]; }
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 addTriggerEvent(options){ function addListener(eventName){ addEventListener(window,eventName,function(e){ sendSize(options.eventName,options.eventType); }); }
if(options.eventNames && Array.prototype.map){ options.eventName = options.eventNames[0]; options.eventNames.map(addListener); } else { addListener(options.eventName); }
log('Added event listener: ' + options.eventType); }
function initEventListeners(){ addTriggerEvent({ eventType: 'Animation Start', eventNames: ['animationstart','webkitAnimationStart'] }); addTriggerEvent({ eventType: 'Animation Iteration', eventNames: ['animationiteration','webkitAnimationIteration'] }); addTriggerEvent({ eventType: 'Animation End', eventNames: ['animationend','webkitAnimationEnd'] }); addTriggerEvent({ eventType: 'Device Orientation Change', eventName: 'deviceorientation' }); addTriggerEvent({ eventType: 'Transition End', eventNames: ['transitionend','webkitTransitionEnd','MSTransitionEnd','oTransitionEnd','otransitionend'] }); addTriggerEvent({ eventType: 'Window Clicked', eventName: 'click' }); //addTriggerEvent({ eventType: 'Window Mouse Down', eventName: 'mousedown' });
//addTriggerEvent({ eventType: 'Window Mouse Up', eventName: 'mouseup' });
if('child' === resizeFrom){ addTriggerEvent({ eventType: 'IFrame Resized', eventName: 'resize' }); } }
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 ) { initEventListeners(); 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 setupInPageLinks(){
function getPagePosition (){ return { x: (window.pageXOffset !== undefined) ? window.pageXOffset : document.documentElement.scrollLeft, y: (window.pageYOffset !== undefined) ? window.pageYOffset : document.documentElement.scrollTop }; }
function getElementPosition(el){ var elPosition = el.getBoundingClientRect(), pagePosition = getPagePosition();
return { x: parseInt(elPosition.left,10) + parseInt(pagePosition.x,10), y: parseInt(elPosition.top,10) + parseInt(pagePosition.y,10) }; }
function findTarget(location){ var hash = location.split("#")[1] || ""; var hashData = decodeURIComponent(hash);
function jumpToTarget(target){ var jumpPosition = getElementPosition(target);
log('Moving to in page link (#'+hash+') at x: '+jumpPosition.x+' y: '+jumpPosition.y); sendMsg(jumpPosition.y, jumpPosition.x, 'scrollToOffset'); // X&Y reversed at sendMsg uses height/width
}
var target = document.getElementById(hashData) || document.getElementsByName(hashData)[0];
if (target){ jumpToTarget(target); } else { log('In page link (#' + hash + ') not found in iFrame, so sending to parent'); sendMsg(0,0,'inPageLink','#'+hash); } }
function checkLocationHash(){ if ('' !== location.hash && '#' !== location.hash){ findTarget(location.href); } }
function bindAnchors(){ function setupLink(el){ function linkClicked(e){ e.preventDefault();
/*jshint validthis:true */ findTarget(this.getAttribute('href')); }
if ('#' !== el.getAttribute('href')){ addEventListener(el,'click',linkClicked); } }
Array.prototype.forEach.call( document.querySelectorAll( 'a[href^="#"]' ), setupLink ); }
function bindLocationHash(){ addEventListener(window,'hashchange',checkLocationHash); }
function initCheck(){ //check if page loaded with location hash after init resize
setTimeout(checkLocationHash,eventCancelTimer); }
function enableInPageLinks(){ if(Array.prototype.forEach && document.querySelectorAll){ log('Setting up location.hash handlers'); bindAnchors(); bindLocationHash(); initCheck(); } else { warn('In page linking not fully supported in this browser! (See README.md for IE8 workaround)'); } }
if(inPageLinks.enable){ enableInPageLinks(); } else { log('In page linking not enabled'); }
return { findTarget:findTarget }; }
function setupPublicMethods(){ if (publicMethods) { log('Enable public methods');
window.parentIFrame = { close: function closeF(){ sendMsg(0,0,'close'); }, getId: function getIdF(){ return myID; }, moveToAnchor: function moveToAnchorF(hash){ inPageLinks.findTarget(hash); }, reset: function resetF(){ resetIFrame('parentIFrame.reset'); }, scrollTo: function scrollToF(x,y){ sendMsg(y,x,'scrollTo'); // X&Y reversed at sendMsg uses height/width
}, scrollToOffset: function scrollToF(x,y){ sendMsg(y,x,'scrollToOffset'); // X&Y reversed at sendMsg uses height/width
}, sendMessage: function sendMessageF(msg,targetOrigin){ sendMsg(0,0,'message',JSON.stringify(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 setupInjectElementLoadListeners(mutations){ function addLoadListener(element){ if (element.height === undefined || element.width === undefined || 0 === element.height || 0 === element.width){ log('Attach listener 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); setupInjectElementLoadListeners(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 checkTolerance(a,b){ var retVal = Math.abs(a-b) <= tolerance; return !retVal; }
currentHeight = (undefined !== customHeight) ? customHeight : getHeight[heightCalcMode](); currentWidth = (undefined !== customWidth ) ? customWidth : getWidth();
return checkTolerance(height,currentHeight) || (calculateWidth && checkTolerance(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; target = event.source;
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 resizeFromParent(){ sendSize('resizeParent','Parent window resized'); }
function getMessageType(){ return event.data.split(']')[1]; }
function isMiddleTier(){ return ('iFrameResize' in window); }
function isInitMsg(){ //test if this message is from a child below us. This is an ugly test, however, updating
//the message format would break backwards compatibility.
return event.data.split(':')[2] in {'true':1,'false':1}; }
if (isMessageForUs()){ if (firstRun === false) { if ('reset' === getMessageType()){ resetFromParent(); } else if ('resize' === getMessageType()){ resizeFromParent(); } else if (event.data !== initMsg && !isMiddleTier()){ warn('Unexpected message ('+event.data+')'); } } else if (isInitMsg()) { initFromParent(); } else { warn('Received message of type ('+getMessageType()+') before initialization.'); } } }
addEventListener(window, 'message', receiver);
})();
|