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