4385f1acbc
Ticket #56
642 lines
No EOL
18 KiB
JavaScript
642 lines
No EOL
18 KiB
JavaScript
/*
|
|
* File: iframeResizer.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
|
|
* Contributor: Reed Dadoune - reed@dadoune.com
|
|
*/
|
|
;(function() {
|
|
'use strict';
|
|
|
|
var
|
|
count = 0,
|
|
firstRun = true,
|
|
logEnabled = false,
|
|
msgHeader = 'message',
|
|
msgHeaderLen = msgHeader.length,
|
|
msgId = '[iFrameSizer]', //Must match iframe msg ID
|
|
msgIdLen = msgId.length,
|
|
pagePosition = null,
|
|
requestAnimationFrame = window.requestAnimationFrame,
|
|
resetRequiredMethods = {max:1,scroll:1,bodyScroll:1,documentElementScroll:1},
|
|
settings = {},
|
|
timer = null,
|
|
|
|
defaults = {
|
|
autoResize : true,
|
|
bodyBackground : null,
|
|
bodyMargin : null,
|
|
bodyMarginV1 : 8,
|
|
bodyPadding : null,
|
|
checkOrigin : true,
|
|
enableInPageLinks : false,
|
|
enablePublicMethods : false,
|
|
heightCalculationMethod : 'offset',
|
|
interval : 32,
|
|
log : false,
|
|
maxHeight : Infinity,
|
|
maxWidth : Infinity,
|
|
minHeight : 0,
|
|
minWidth : 0,
|
|
resizeFrom : 'parent',
|
|
scrolling : false,
|
|
sizeHeight : true,
|
|
sizeWidth : false,
|
|
tolerance : 0,
|
|
closedCallback : function(){},
|
|
initCallback : function(){},
|
|
messageCallback : function(){},
|
|
resizedCallback : function(){},
|
|
scrollCallback : function(){return true;}
|
|
};
|
|
|
|
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 getMyID(){
|
|
var retStr = 'Host page';
|
|
|
|
if (window.top!==window.self){
|
|
if (window.parentIFrame){
|
|
retStr = window.parentIFrame.getId();
|
|
} else {
|
|
retStr = 'Nested host page';
|
|
}
|
|
}
|
|
|
|
return retStr;
|
|
}
|
|
|
|
function formatLogMsg(msg){
|
|
return msgId + '[' + getMyID() + ']' + msg;
|
|
}
|
|
|
|
function log(msg){
|
|
if (logEnabled && ('object' === typeof window.console)){
|
|
console.log(formatLogMsg(msg));
|
|
}
|
|
}
|
|
|
|
function warn(msg){
|
|
if ('object' === typeof window.console){
|
|
console.warn(formatLogMsg(msg));
|
|
}
|
|
}
|
|
|
|
function iFrameListener(event){
|
|
function resizeIFrame(){
|
|
function resize(){
|
|
setSize(messageData);
|
|
setPagePosition();
|
|
settings[iframeID].resizedCallback(messageData);
|
|
}
|
|
|
|
ensureInRange('Height');
|
|
ensureInRange('Width');
|
|
|
|
syncResize(resize,messageData,'resetPage');
|
|
}
|
|
|
|
function closeIFrame(iframe){
|
|
var iframeID = iframe.id;
|
|
|
|
log(' Removing iFrame: '+iframeID);
|
|
iframe.parentNode.removeChild(iframe);
|
|
settings[iframeID].closedCallback(iframeID);
|
|
delete settings[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[iframeID]['max'+Dimension]),
|
|
min = Number(settings[iframeID]['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(){
|
|
function checkAllowedOrigin(){
|
|
function checkList(){
|
|
log(' Checking connection is from allowed list of origins: ' + checkOrigin);
|
|
var i;
|
|
for (i = 0; i < checkOrigin.length; i++) {
|
|
if (checkOrigin[i] === origin) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function checkSingle(){
|
|
log(' Checking connection is from: '+remoteHost);
|
|
return origin == remoteHost;
|
|
}
|
|
|
|
return checkOrigin.constructor === Array ? checkList() : checkSingle();
|
|
}
|
|
|
|
var
|
|
origin = event.origin,
|
|
checkOrigin = settings[iframeID].checkOrigin,
|
|
remoteHost = messageData.iframe.src.split('/').slice(0,3).join('/');
|
|
|
|
if (checkOrigin) {
|
|
if ((''+origin !== 'null') && !checkAllowedOrigin()) {
|
|
throw new Error(
|
|
'Unexpected message received from: ' + origin +
|
|
' for ' + messageData.iframe.id +
|
|
'. Message was: ' + event.data +
|
|
'. This error can be disabled by setting the checkOrigin: false option or by providing of array of trusted domains.'
|
|
);
|
|
}
|
|
}
|
|
|
|
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,'undefined':1};
|
|
|
|
if (retCode){
|
|
log(' Ignoring init message from meta parent page');
|
|
}
|
|
|
|
return retCode;
|
|
}
|
|
|
|
function getMsgBody(offset){
|
|
return msg.substr(msg.indexOf(':')+msgHeaderLen+offset);
|
|
}
|
|
|
|
function forwardMsgFromIFrame(msgBody){
|
|
log(' MessageCallback passed: {iframe: '+ messageData.iframe.id + ', message: ' + msgBody + '}');
|
|
settings[iframeID].messageCallback({
|
|
iframe: messageData.iframe,
|
|
message: JSON.parse(msgBody)
|
|
});
|
|
log(' --');
|
|
}
|
|
|
|
function checkIFrameExists(){
|
|
if (null === messageData.iframe) {
|
|
warn(' IFrame ('+messageData.id+') not found');
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function getElementPosition(target){
|
|
var
|
|
iFramePosition = target.getBoundingClientRect();
|
|
|
|
getPagePosition();
|
|
|
|
return {
|
|
x: parseInt(iFramePosition.left, 10) + parseInt(pagePosition.x, 10),
|
|
y: parseInt(iFramePosition.top, 10) + parseInt(pagePosition.y, 10)
|
|
};
|
|
}
|
|
|
|
function scrollRequestFromChild(addOffset){
|
|
function reposition(){
|
|
pagePosition = newPosition;
|
|
|
|
scrollTo();
|
|
|
|
log(' --');
|
|
}
|
|
|
|
function calcOffset(){
|
|
return {
|
|
x: Number(messageData.width) + offset.x,
|
|
y: Number(messageData.height) + offset.y
|
|
};
|
|
}
|
|
|
|
var
|
|
offset = addOffset ? getElementPosition(messageData.iframe) : {x:0,y:0},
|
|
newPosition = calcOffset();
|
|
|
|
log(' Reposition requested from iFrame (offset x:'+offset.x+' y:'+offset.y+')');
|
|
|
|
if(window.top!==window.self){
|
|
if (window.parentIFrame){
|
|
if (addOffset){
|
|
parentIFrame.scrollToOffset(newPosition.x,newPosition.y);
|
|
} else {
|
|
parentIFrame.scrollTo(messageData.width,messageData.height);
|
|
}
|
|
} else {
|
|
warn(' Unable to scroll to requested position, window.parentIFrame not found');
|
|
}
|
|
} else {
|
|
reposition();
|
|
}
|
|
|
|
}
|
|
|
|
function scrollTo(){
|
|
if (false !== settings[iframeID].scrollCallback(pagePosition)){
|
|
setPagePosition();
|
|
}
|
|
}
|
|
|
|
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);
|
|
pagePosition = {
|
|
x: jumpPosition.x,
|
|
y: jumpPosition.y
|
|
};
|
|
|
|
scrollTo();
|
|
log(' --');
|
|
}
|
|
|
|
var target = document.getElementById(hashData) || document.getElementsByName(hashData)[0];
|
|
|
|
if(window.top!==window.self){
|
|
if (window.parentIFrame){
|
|
parentIFrame.moveToAnchor(hash);
|
|
} else {
|
|
log(' In page link #'+hash+' not found and window.parentIFrame not found');
|
|
}
|
|
} else if (target){
|
|
jumpToTarget(target);
|
|
} else {
|
|
log(' In page link #'+hash+' not found');
|
|
}
|
|
}
|
|
|
|
function actionMsg(){
|
|
switch(messageData.type){
|
|
case 'close':
|
|
closeIFrame(messageData.iframe);
|
|
break;
|
|
case 'message':
|
|
forwardMsgFromIFrame(getMsgBody(6));
|
|
break;
|
|
case 'scrollTo':
|
|
scrollRequestFromChild(false);
|
|
break;
|
|
case 'scrollToOffset':
|
|
scrollRequestFromChild(true);
|
|
break;
|
|
case 'inPageLink':
|
|
findTarget(getMsgBody(9));
|
|
break;
|
|
case 'reset':
|
|
resetIFrame(messageData);
|
|
break;
|
|
case 'init':
|
|
resizeIFrame();
|
|
settings[iframeID].initCallback(messageData.iframe);
|
|
break;
|
|
default:
|
|
resizeIFrame();
|
|
}
|
|
}
|
|
|
|
function hasSettings(iframeID){
|
|
var retBool = true;
|
|
|
|
if (!settings[iframeID]){
|
|
retBool = false;
|
|
warn(messageData.type + ' No settings for ' + iframeID + '. Message was: ' + msg);
|
|
}
|
|
|
|
return retBool;
|
|
}
|
|
|
|
var
|
|
msg = event.data,
|
|
messageData = {},
|
|
iframeID = null;
|
|
|
|
if (isMessageForUs()){
|
|
messageData = processMsg();
|
|
iframeID = messageData.id;
|
|
|
|
if (!isMessageFromMetaParent() && hasSettings(iframeID)){
|
|
logEnabled = settings[iframeID].log;
|
|
log(' Received: '+msg);
|
|
|
|
if ( 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 page position: '+pagePosition.x+','+pagePosition.y);
|
|
}
|
|
}
|
|
|
|
function setPagePosition(){
|
|
if(null !== pagePosition){
|
|
window.scrollTo(pagePosition.x,pagePosition.y);
|
|
log(' Set page position: '+pagePosition.x+','+pagePosition.y);
|
|
pagePosition = null;
|
|
}
|
|
}
|
|
|
|
function resetIFrame(messageData){
|
|
function reset(){
|
|
setSize(messageData);
|
|
trigger('reset','reset',messageData.iframe,messageData.id);
|
|
}
|
|
|
|
log(' Size reset requested by '+('init'===messageData.type?'host page':'iFrame'));
|
|
getPagePosition();
|
|
syncResize(reset,messageData,'init');
|
|
}
|
|
|
|
function setSize(messageData){
|
|
function setDimension(dimension){
|
|
messageData.iframe.style[dimension] = messageData[dimension] + 'px';
|
|
log(
|
|
' IFrame (' + iframeID +
|
|
') ' + dimension +
|
|
' set to ' + messageData[dimension] + 'px'
|
|
);
|
|
}
|
|
var iframeID = messageData.iframe.id;
|
|
if( settings[iframeID].sizeHeight) { setDimension('height'); }
|
|
if( settings[iframeID].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,id){
|
|
if(iframe && iframe.contentWindow){
|
|
log('[' + calleeMsg + '] Sending msg to iframe ('+msg+')');
|
|
iframe.contentWindow.postMessage( msgId + msg, '*' );
|
|
} else {
|
|
warn('[' + calleeMsg + '] IFrame not found');
|
|
if(settings[id]) delete settings[id];
|
|
}
|
|
}
|
|
|
|
|
|
function setupIFrame(options){
|
|
function setLimits(){
|
|
function addStyle(style){
|
|
if ((Infinity !== settings[iframeID][style]) && (0 !== settings[iframeID][style])){
|
|
iframe.style[style] = settings[iframeID][style] + 'px';
|
|
log(' Set '+style+' = '+settings[iframeID][style]+'px');
|
|
}
|
|
}
|
|
|
|
addStyle('maxHeight');
|
|
addStyle('minHeight');
|
|
addStyle('maxWidth');
|
|
addStyle('minWidth');
|
|
}
|
|
|
|
function ensureHasId(iframeID){
|
|
if (''===iframeID){
|
|
iframe.id = iframeID = 'iFrameResizer' + count++;
|
|
logEnabled = (options || {}).log;
|
|
log(' Added missing iframe ID: '+ iframeID +' (' + iframe.src + ')');
|
|
}
|
|
|
|
return iframeID;
|
|
}
|
|
|
|
function setScrolling(){
|
|
log(' IFrame scrolling ' + (settings[iframeID].scrolling ? 'enabled' : 'disabled') + ' for ' + iframeID);
|
|
iframe.style.overflow = false === settings[iframeID].scrolling ? 'hidden' : 'auto';
|
|
iframe.scrolling = false === settings[iframeID].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[iframeID].bodyMargin)) || ('0'===settings[iframeID].bodyMargin)){
|
|
settings[iframeID].bodyMarginV1 = settings[iframeID].bodyMargin;
|
|
settings[iframeID].bodyMargin = '' + settings[iframeID].bodyMargin + 'px';
|
|
}
|
|
}
|
|
|
|
function createOutgoingMsg(){
|
|
return iframeID +
|
|
':' + settings[iframeID].bodyMarginV1 +
|
|
':' + settings[iframeID].sizeWidth +
|
|
':' + settings[iframeID].log +
|
|
':' + settings[iframeID].interval +
|
|
':' + settings[iframeID].enablePublicMethods +
|
|
':' + settings[iframeID].autoResize +
|
|
':' + settings[iframeID].bodyMargin +
|
|
':' + settings[iframeID].heightCalculationMethod +
|
|
':' + settings[iframeID].bodyBackground +
|
|
':' + settings[iframeID].bodyPadding +
|
|
':' + settings[iframeID].tolerance +
|
|
':' + settings[iframeID].enableInPageLinks +
|
|
':' + settings[iframeID].resizeFrom;
|
|
}
|
|
|
|
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[iframeID].heightCalculationMethod in resetRequiredMethods){
|
|
resetIFrame({
|
|
iframe:iframe,
|
|
height:0,
|
|
width:0,
|
|
type:'init'
|
|
});
|
|
}
|
|
});
|
|
trigger('init',msg,iframe);
|
|
}
|
|
|
|
function checkOptions(options){
|
|
if ('object' !== typeof options){
|
|
throw new TypeError('Options is not an object.');
|
|
}
|
|
}
|
|
|
|
function processOptions(options){
|
|
options = options || {};
|
|
settings[iframeID] = {};
|
|
|
|
checkOptions(options);
|
|
|
|
for (var option in defaults) {
|
|
if (defaults.hasOwnProperty(option)){
|
|
settings[iframeID][option] = options.hasOwnProperty(option) ? options[option] : defaults[option];
|
|
}
|
|
}
|
|
|
|
logEnabled = settings[iframeID].log;
|
|
}
|
|
|
|
var
|
|
/*jshint validthis:true */
|
|
iframe = this,
|
|
iframeID = ensureHasId(iframe.id);
|
|
|
|
processOptions(options);
|
|
setScrolling();
|
|
setLimits();
|
|
setupBodyMarginValues();
|
|
init(createOutgoingMsg());
|
|
}
|
|
|
|
function throttle(fn,time){
|
|
if (null === timer){
|
|
timer = setTimeout(function(){
|
|
timer = null;
|
|
fn();
|
|
}, time);
|
|
}
|
|
}
|
|
|
|
function winResize(){
|
|
throttle(function(){
|
|
for (var iframeId in settings){
|
|
if('parent' === settings[iframeId].resizeFrom){
|
|
trigger('Window resize','resize',document.getElementById(iframeId),iframeId);
|
|
}
|
|
}
|
|
},66);
|
|
}
|
|
|
|
function factory(){
|
|
|
|
setupRequestAnimationFrame();
|
|
addEventListener(window,'message',iFrameListener);
|
|
addEventListener(window,'resize', winResize);
|
|
|
|
function init(element, options){
|
|
if(!element.tagName) {
|
|
throw new TypeError('Object is not a valid DOM element');
|
|
} else if ('IFRAME' !== element.tagName.toUpperCase()) {
|
|
throw new TypeError('Expected <IFRAME> tag, found <'+element.tagName+'>.');
|
|
} else {
|
|
setupIFrame.call(element, options);
|
|
}
|
|
}
|
|
|
|
return function iFrameResizeF(options,target){
|
|
switch (typeof(target)){
|
|
case 'undefined':
|
|
case 'string':
|
|
Array.prototype.forEach.call( document.querySelectorAll( target || 'iframe' ), function (element) {
|
|
init(element, options);
|
|
});
|
|
break;
|
|
case 'object':
|
|
init(target, options);
|
|
break;
|
|
default:
|
|
throw new TypeError('Unexpected data type ('+typeof(target)+').');
|
|
}
|
|
};
|
|
}
|
|
|
|
function createJQueryPublicMethod($){
|
|
$.fn.iFrameResize = function $iFrameResizeF(options) {
|
|
return this.filter('iframe').each(function (index, element) {
|
|
setupIFrame.call(element, options);
|
|
}).end();
|
|
};
|
|
}
|
|
|
|
if (window.jQuery) { createJQueryPublicMethod(jQuery); }
|
|
|
|
if (typeof define === 'function' && define.amd) {
|
|
define([],factory);
|
|
} else if (typeof module === 'object' && typeof module.exports === 'object') { //Node for browserfy
|
|
module.exports = factory();
|
|
} else {
|
|
window.iFrameResize = window.iFrameResize || factory();
|
|
}
|
|
|
|
})(); |