Mercurial > repos > saskia-hiltemann > ireport
comparison iframe-resizer/src/iframeResizer.js @ 0:ac5f9272033b draft
first upload
author | saskia-hiltemann |
---|---|
date | Tue, 01 Jul 2014 11:42:23 -0400 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:ac5f9272033b |
---|---|
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 })(); |