├── .gitattributes
├── README.md
├── docs
├── .nojekyll
├── css
│ ├── semantic.css
│ └── semantic.min.css
├── images
│ ├── avatar
│ │ └── avatar.jpg
│ └── icons
│ │ └── tampermonkey.png
├── index.html
└── js
│ ├── iframe-content.js
│ ├── iframe.js
│ ├── jquery.min.js
│ ├── semantic.js
│ ├── semantic.min.js
│ └── vue.js
├── src
└── wcc.js
└── 主要数据来源.pdf
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # What-Class-Classification
2 | 便捷地辨别北京大学通选课程类别!
3 |
4 | 效果展示:[树洞#1673516](http://pkuhelper.pku.edu.cn/hole/#1673516)
5 |
6 | ### Update
7 |
8 | 2. 增加了F类通选。([#2](https://github.com/wr786/What-Class-Classification/issues/2))
9 | 1. 修复了《中俄文化交流史》的课程类别。([#1](https://github.com/wr786/What-Class-Classification/issues/1))
10 |
11 | ### Usage
12 |
13 | - 详见[该推送](https://mp.weixin.qq.com/s/pey6Y2QyboGn8gdHnADnpg)
14 |
15 |
--------------------------------------------------------------------------------
/docs/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wr786/What-Class-Classification/b92736be720ddbc3cd054e9c2bc5eba409e93458/docs/.nojekyll
--------------------------------------------------------------------------------
/docs/images/avatar/avatar.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wr786/What-Class-Classification/b92736be720ddbc3cd054e9c2bc5eba409e93458/docs/images/avatar/avatar.jpg
--------------------------------------------------------------------------------
/docs/images/icons/tampermonkey.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wr786/What-Class-Classification/b92736be720ddbc3cd054e9c2bc5eba409e93458/docs/images/icons/tampermonkey.png
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | What Class Classification | wr786
8 |
9 |
10 |
11 |
12 |
13 |
14 |
31 |
32 |
33 |
34 |
35 |
38 |
39 |
40 |
wr786
41 |
42 |
43 |
44 |
点击获取脚本
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
330 |
331 |
--------------------------------------------------------------------------------
/docs/js/iframe-content.js:
--------------------------------------------------------------------------------
1 | /*
2 | * File: iframeResizer.contentWindow.js
3 | * Desc: Include this file in any page being loaded into an iframe
4 | * to force the iframe to resize to the content size.
5 | * Requires: iframeResizer.js on host page.
6 | * Author: David J. Bradshaw - dave@bradshaw.net
7 | * Contributor: Jure Mav - jure.mav@gmail.com
8 | * Contributor: Ian Caunce - ian@hallnet.co.uk
9 | */
10 |
11 | ;(function() {
12 | 'use strict';
13 |
14 | var
15 | autoResize = true,
16 | base = 10,
17 | bodyBackground = '',
18 | bodyMargin = 0,
19 | bodyMarginStr = '',
20 | bodyPadding = '',
21 | calculateWidth = false,
22 | doubleEventList = {'resize':1,'click':1},
23 | eventCancelTimer = 128,
24 | height = 1,
25 | firstRun = true,
26 | heightCalcModeDefault = 'offset',
27 | heightCalcMode = heightCalcModeDefault,
28 | initLock = true,
29 | initMsg = '',
30 | inPageLinks = {},
31 | interval = 32,
32 | logging = false,
33 | msgID = '[iFrameSizer]', //Must match host page msg ID
34 | msgIdLen = msgID.length,
35 | myID = '',
36 | publicMethods = false,
37 | resetRequiredMethods = {max:1,scroll:1,bodyScroll:1,documentElementScroll:1},
38 | resizeFrom = 'parent',
39 | targetOriginDefault = '*',
40 | target = window.parent,
41 | tolerance = 0,
42 | triggerLocked = false,
43 | triggerLockedTimer = null,
44 | width = 1;
45 |
46 |
47 | function addEventListener(el,evt,func){
48 | if ('addEventListener' in window){
49 | el.addEventListener(evt,func, false);
50 | } else if ('attachEvent' in window){ //IE
51 | el.attachEvent('on'+evt,func);
52 | }
53 | }
54 |
55 | function formatLogMsg(msg){
56 | return msgID + '[' + myID + ']' + ' ' + msg;
57 | }
58 |
59 | function log(msg){
60 | if (logging && ('object' === typeof window.console)){
61 | console.log(formatLogMsg(msg));
62 | }
63 | }
64 |
65 | function warn(msg){
66 | if ('object' === typeof window.console){
67 | console.warn(formatLogMsg(msg));
68 | }
69 | }
70 |
71 |
72 | function init(){
73 | log('Initializing iFrame');
74 | readData();
75 | setMargin();
76 | setBodyStyle('background',bodyBackground);
77 | setBodyStyle('padding',bodyPadding);
78 | injectClearFixIntoBodyElement();
79 | checkHeightMode();
80 | stopInfiniteResizingOfIFrame();
81 | setupPublicMethods();
82 | startEventListeners();
83 | inPageLinks = setupInPageLinks();
84 | sendSize('init','Init message from host page');
85 | }
86 |
87 | function readData(){
88 |
89 | var data = initMsg.substr(msgIdLen).split(':');
90 |
91 | function strBool(str){
92 | return 'true' === str ? true : false;
93 | }
94 |
95 | myID = data[0];
96 | bodyMargin = (undefined !== data[1]) ? Number(data[1]) : bodyMargin; //For V1 compatibility
97 | calculateWidth = (undefined !== data[2]) ? strBool(data[2]) : calculateWidth;
98 | logging = (undefined !== data[3]) ? strBool(data[3]) : logging;
99 | interval = (undefined !== data[4]) ? Number(data[4]) : interval;
100 | publicMethods = (undefined !== data[5]) ? strBool(data[5]) : publicMethods;
101 | autoResize = (undefined !== data[6]) ? strBool(data[6]) : autoResize;
102 | bodyMarginStr = data[7];
103 | heightCalcMode = (undefined !== data[8]) ? data[8] : heightCalcMode;
104 | bodyBackground = data[9];
105 | bodyPadding = data[10];
106 | tolerance = (undefined !== data[11]) ? Number(data[11]) : tolerance;
107 | inPageLinks.enable = (undefined !== data[12]) ? strBool(data[12]): false;
108 | resizeFrom = data[13];
109 | }
110 |
111 | function chkCSS(attr,value){
112 | if (-1 !== value.indexOf('-')){
113 | warn('Negative CSS value ignored for '+attr);
114 | value='';
115 | }
116 | return value;
117 | }
118 |
119 | function setBodyStyle(attr,value){
120 | if ((undefined !== value) && ('' !== value) && ('null' !== value)){
121 | document.body.style[attr] = value;
122 | log('Body '+attr+' set to "'+value+'"');
123 | }
124 | }
125 |
126 | function setMargin(){
127 | //If called via V1 script, convert bodyMargin from int to str
128 | if (undefined === bodyMarginStr){
129 | bodyMarginStr = bodyMargin+'px';
130 | }
131 | chkCSS('margin',bodyMarginStr);
132 | setBodyStyle('margin',bodyMarginStr);
133 | }
134 |
135 | function stopInfiniteResizingOfIFrame(){
136 | document.documentElement.style.height = '';
137 | document.body.style.height = '';
138 | log('HTML & body height set to "auto"');
139 | }
140 |
141 |
142 | function addTriggerEvent(options){
143 | function addListener(eventName){
144 | addEventListener(window,eventName,function(e){
145 | sendSize(options.eventName,options.eventType);
146 | });
147 | }
148 |
149 | if(options.eventNames && Array.prototype.map){
150 | options.eventName = options.eventNames[0];
151 | options.eventNames.map(addListener);
152 | } else {
153 | addListener(options.eventName);
154 | }
155 |
156 | log('Added event listener: ' + options.eventType);
157 | }
158 |
159 | function initEventListeners(){
160 | addTriggerEvent({ eventType: 'Animation Start', eventNames: ['animationstart','webkitAnimationStart'] });
161 | addTriggerEvent({ eventType: 'Animation Iteration', eventNames: ['animationiteration','webkitAnimationIteration'] });
162 | addTriggerEvent({ eventType: 'Animation End', eventNames: ['animationend','webkitAnimationEnd'] });
163 | addTriggerEvent({ eventType: 'Device Orientation Change', eventName: 'deviceorientation' });
164 | addTriggerEvent({ eventType: 'Transition End', eventNames: ['transitionend','webkitTransitionEnd','MSTransitionEnd','oTransitionEnd','otransitionend'] });
165 | addTriggerEvent({ eventType: 'Window Clicked', eventName: 'click' });
166 | //addTriggerEvent({ eventType: 'Window Mouse Down', eventName: 'mousedown' });
167 | //addTriggerEvent({ eventType: 'Window Mouse Up', eventName: 'mouseup' });
168 | if('child' === resizeFrom){
169 | addTriggerEvent({ eventType: 'IFrame Resized', eventName: 'resize' });
170 | }
171 | }
172 |
173 | function checkHeightMode(){
174 | if (heightCalcModeDefault !== heightCalcMode){
175 | if (!(heightCalcMode in getHeight)){
176 | warn(heightCalcMode + ' is not a valid option for heightCalculationMethod.');
177 | heightCalcMode='bodyScroll';
178 | }
179 | log('Height calculation method set to "'+heightCalcMode+'"');
180 | }
181 | }
182 |
183 | function startEventListeners(){
184 | if ( true === autoResize ) {
185 | initEventListeners();
186 | setupMutationObserver();
187 | }
188 | else {
189 | log('Auto Resize disabled');
190 | }
191 | }
192 |
193 | function injectClearFixIntoBodyElement(){
194 | var clearFix = document.createElement('div');
195 | clearFix.style.clear = 'both';
196 | clearFix.style.display = 'block'; //Guard against this having been globally redefined in CSS.
197 | document.body.appendChild(clearFix);
198 | }
199 |
200 | function setupInPageLinks(){
201 |
202 | function getPagePosition (){
203 | return {
204 | x: (window.pageXOffset !== undefined) ? window.pageXOffset : document.documentElement.scrollLeft,
205 | y: (window.pageYOffset !== undefined) ? window.pageYOffset : document.documentElement.scrollTop
206 | };
207 | }
208 |
209 | function getElementPosition(el){
210 | var
211 | elPosition = el.getBoundingClientRect(),
212 | pagePosition = getPagePosition();
213 |
214 | return {
215 | x: parseInt(elPosition.left,10) + parseInt(pagePosition.x,10),
216 | y: parseInt(elPosition.top,10) + parseInt(pagePosition.y,10)
217 | };
218 | }
219 |
220 | function findTarget(location){
221 | var hash = location.split("#")[1] || "";
222 | var hashData = decodeURIComponent(hash);
223 |
224 | function jumpToTarget(target){
225 | var jumpPosition = getElementPosition(target);
226 |
227 | log('Moving to in page link (#'+hash+') at x: '+jumpPosition.x+' y: '+jumpPosition.y);
228 | sendMsg(jumpPosition.y, jumpPosition.x, 'scrollToOffset'); // X&Y reversed at sendMsg uses height/width
229 | }
230 |
231 | var target = document.getElementById(hashData) || document.getElementsByName(hashData)[0];
232 |
233 | if (target){
234 | jumpToTarget(target);
235 | } else {
236 | log('In page link (#' + hash + ') not found in iFrame, so sending to parent');
237 | sendMsg(0,0,'inPageLink','#'+hash);
238 | }
239 | }
240 |
241 | function checkLocationHash(){
242 | if ('' !== location.hash && '#' !== location.hash){
243 | findTarget(location.href);
244 | }
245 | }
246 |
247 | function bindAnchors(){
248 | function setupLink(el){
249 | function linkClicked(e){
250 | e.preventDefault();
251 |
252 | /*jshint validthis:true */
253 | findTarget(this.getAttribute('href'));
254 | }
255 |
256 | if ('#' !== el.getAttribute('href')){
257 | addEventListener(el,'click',linkClicked);
258 | }
259 | }
260 |
261 | Array.prototype.forEach.call( document.querySelectorAll( 'a[href^="#"]' ), setupLink );
262 | }
263 |
264 | function bindLocationHash(){
265 | addEventListener(window,'hashchange',checkLocationHash);
266 | }
267 |
268 | function initCheck(){ //check if page loaded with location hash after init resize
269 | setTimeout(checkLocationHash,eventCancelTimer);
270 | }
271 |
272 | function enableInPageLinks(){
273 | if(Array.prototype.forEach && document.querySelectorAll){
274 | log('Setting up location.hash handlers');
275 | bindAnchors();
276 | bindLocationHash();
277 | initCheck();
278 | } else {
279 | warn('In page linking not fully supported in this browser! (See README.md for IE8 workaround)');
280 | }
281 | }
282 |
283 | if(inPageLinks.enable){
284 | enableInPageLinks();
285 | } else {
286 | log('In page linking not enabled');
287 | }
288 |
289 | return {
290 | findTarget:findTarget
291 | };
292 | }
293 |
294 | function setupPublicMethods(){
295 | if (publicMethods) {
296 | log('Enable public methods');
297 |
298 | window.parentIFrame = {
299 | close: function closeF(){
300 | sendMsg(0,0,'close');
301 | },
302 | getId: function getIdF(){
303 | return myID;
304 | },
305 | moveToAnchor: function moveToAnchorF(hash){
306 | inPageLinks.findTarget(hash);
307 | },
308 | reset: function resetF(){
309 | resetIFrame('parentIFrame.reset');
310 | },
311 | scrollTo: function scrollToF(x,y){
312 | sendMsg(y,x,'scrollTo'); // X&Y reversed at sendMsg uses height/width
313 | },
314 | scrollToOffset: function scrollToF(x,y){
315 | sendMsg(y,x,'scrollToOffset'); // X&Y reversed at sendMsg uses height/width
316 | },
317 | sendMessage: function sendMessageF(msg,targetOrigin){
318 | sendMsg(0,0,'message',JSON.stringify(msg),targetOrigin);
319 | },
320 | setHeightCalculationMethod: function setHeightCalculationMethodF(heightCalculationMethod){
321 | heightCalcMode = heightCalculationMethod;
322 | checkHeightMode();
323 | },
324 | setTargetOrigin: function setTargetOriginF(targetOrigin){
325 | log('Set targetOrigin: '+targetOrigin);
326 | targetOriginDefault = targetOrigin;
327 | },
328 | size: function sizeF(customHeight, customWidth){
329 | var valString = ''+(customHeight?customHeight:'')+(customWidth?','+customWidth:'');
330 | lockTrigger();
331 | sendSize('size','parentIFrame.size('+valString+')', customHeight, customWidth);
332 | }
333 | };
334 | }
335 | }
336 |
337 | function initInterval(){
338 | if ( 0 !== interval ){
339 | log('setInterval: '+interval+'ms');
340 | setInterval(function(){
341 | sendSize('interval','setInterval: '+interval);
342 | },Math.abs(interval));
343 | }
344 | }
345 |
346 | function setupInjectElementLoadListeners(mutations){
347 | function addLoadListener(element){
348 | if (element.height === undefined || element.width === undefined || 0 === element.height || 0 === element.width){
349 | log('Attach listener to '+element.src);
350 | addEventListener(element,'load', function imageLoaded(){
351 | sendSize('imageLoad','Image loaded');
352 | });
353 | }
354 | }
355 |
356 | mutations.forEach(function (mutation) {
357 | if (mutation.type === 'attributes' && mutation.attributeName === 'src'){
358 | addLoadListener(mutation.target);
359 | } else if (mutation.type === 'childList'){
360 | var images = mutation.target.querySelectorAll('img');
361 | Array.prototype.forEach.call(images,function (image) {
362 | addLoadListener(image);
363 | });
364 | }
365 | });
366 | }
367 |
368 | function setupMutationObserver(){
369 |
370 | var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
371 |
372 | function createMutationObserver(){
373 | var
374 | target = document.querySelector('body'),
375 |
376 | config = {
377 | attributes : true,
378 | attributeOldValue : false,
379 | characterData : true,
380 | characterDataOldValue : false,
381 | childList : true,
382 | subtree : true
383 | },
384 |
385 | observer = new MutationObserver(function(mutations) {
386 | sendSize('mutationObserver','mutationObserver: ' + mutations[0].target + ' ' + mutations[0].type);
387 | setupInjectElementLoadListeners(mutations); //Deal with WebKit asyncing image loading when tags are injected into the page
388 | });
389 |
390 | log('Enable MutationObserver');
391 | observer.observe(target, config);
392 | }
393 |
394 | if (MutationObserver){
395 | if (0 > interval) {
396 | initInterval();
397 | } else {
398 | createMutationObserver();
399 | }
400 | }
401 | else {
402 | warn('MutationObserver not supported in this browser!');
403 | initInterval();
404 | }
405 | }
406 |
407 |
408 | // document.documentElement.offsetHeight is not reliable, so
409 | // we have to jump through hoops to get a better value.
410 | function getBodyOffsetHeight(){
411 | function getComputedBodyStyle(prop) {
412 | function convertUnitsToPxForIE8(value) {
413 | var PIXEL = /^\d+(px)?$/i;
414 |
415 | if (PIXEL.test(value)) {
416 | return parseInt(value,base);
417 | }
418 |
419 | var
420 | style = el.style.left,
421 | runtimeStyle = el.runtimeStyle.left;
422 |
423 | el.runtimeStyle.left = el.currentStyle.left;
424 | el.style.left = value || 0;
425 | value = el.style.pixelLeft;
426 | el.style.left = style;
427 | el.runtimeStyle.left = runtimeStyle;
428 |
429 | return value;
430 | }
431 |
432 | var
433 | el = document.body,
434 | retVal = 0;
435 |
436 | if (('defaultView' in document) && ('getComputedStyle' in document.defaultView)) {
437 | retVal = document.defaultView.getComputedStyle(el, null);
438 | retVal = (null !== retVal) ? retVal[prop] : 0;
439 | } else {//IE8
440 | retVal = convertUnitsToPxForIE8(el.currentStyle[prop]);
441 | }
442 |
443 | return parseInt(retVal,base);
444 | }
445 |
446 | return document.body.offsetHeight +
447 | getComputedBodyStyle('marginTop') +
448 | getComputedBodyStyle('marginBottom');
449 | }
450 |
451 | function getBodyScrollHeight(){
452 | return document.body.scrollHeight;
453 | }
454 |
455 | function getDEOffsetHeight(){
456 | return document.documentElement.offsetHeight;
457 | }
458 |
459 | function getDEScrollHeight(){
460 | return document.documentElement.scrollHeight;
461 | }
462 |
463 | //From https://github.com/guardian/iframe-messenger
464 | function getLowestElementHeight() {
465 | var
466 | allElements = document.querySelectorAll('body *'),
467 | allElementsLength = allElements.length,
468 | maxBottomVal = 0,
469 | timer = new Date().getTime();
470 |
471 | for (var i = 0; i < allElementsLength; i++) {
472 | if (allElements[i].getBoundingClientRect().bottom > maxBottomVal) {
473 | maxBottomVal = allElements[i].getBoundingClientRect().bottom;
474 | }
475 | }
476 |
477 | timer = new Date().getTime() - timer;
478 |
479 | log('Parsed '+allElementsLength+' HTML elements');
480 | log('LowestElement bottom position calculated in ' + timer + 'ms');
481 |
482 | return maxBottomVal;
483 | }
484 |
485 | function getAllHeights(){
486 | return [
487 | getBodyOffsetHeight(),
488 | getBodyScrollHeight(),
489 | getDEOffsetHeight(),
490 | getDEScrollHeight()
491 | ];
492 | }
493 |
494 | function getMaxHeight(){
495 | return Math.max.apply(null,getAllHeights());
496 | }
497 |
498 | function getMinHeight(){
499 | return Math.min.apply(null,getAllHeights());
500 | }
501 |
502 | function getBestHeight(){
503 | return Math.max(getBodyOffsetHeight(),getLowestElementHeight());
504 | }
505 |
506 | var getHeight = {
507 | offset : getBodyOffsetHeight, //Backward compatability
508 | bodyOffset : getBodyOffsetHeight,
509 | bodyScroll : getBodyScrollHeight,
510 | documentElementOffset : getDEOffsetHeight,
511 | scroll : getDEScrollHeight, //Backward compatability
512 | documentElementScroll : getDEScrollHeight,
513 | max : getMaxHeight,
514 | min : getMinHeight,
515 | grow : getMaxHeight,
516 | lowestElement : getBestHeight
517 | };
518 |
519 | function getWidth(){
520 | return Math.max(
521 | document.documentElement.scrollWidth,
522 | document.body.scrollWidth
523 | );
524 | }
525 |
526 | function sendSize(triggerEvent, triggerEventDesc, customHeight, customWidth){
527 |
528 | var currentHeight,currentWidth;
529 |
530 | function recordTrigger(){
531 | if (!(triggerEvent in {'reset':1,'resetPage':1,'init':1})){
532 | log( 'Trigger event: ' + triggerEventDesc );
533 | }
534 | }
535 |
536 | function resizeIFrame(){
537 | height = currentHeight;
538 | width = currentWidth;
539 |
540 | sendMsg(height,width,triggerEvent);
541 | }
542 |
543 | function isDoubleFiredEvent(){
544 | return triggerLocked && (triggerEvent in doubleEventList);
545 | }
546 |
547 | function isSizeChangeDetected(){
548 | function checkTolerance(a,b){
549 | var retVal = Math.abs(a-b) <= tolerance;
550 | return !retVal;
551 | }
552 |
553 | currentHeight = (undefined !== customHeight) ? customHeight : getHeight[heightCalcMode]();
554 | currentWidth = (undefined !== customWidth ) ? customWidth : getWidth();
555 |
556 | return checkTolerance(height,currentHeight) ||
557 | (calculateWidth && checkTolerance(width,currentWidth));
558 | }
559 |
560 | function isForceResizableEvent(){
561 | return !(triggerEvent in {'init':1,'interval':1,'size':1});
562 | }
563 |
564 | function isForceResizableHeightCalcMode(){
565 | return (heightCalcMode in resetRequiredMethods);
566 | }
567 |
568 | function logIgnored(){
569 | log('No change in size detected');
570 | }
571 |
572 | function checkDownSizing(){
573 | if (isForceResizableEvent() && isForceResizableHeightCalcMode()){
574 | resetIFrame(triggerEventDesc);
575 | } else if (!(triggerEvent in {'interval':1})){
576 | recordTrigger();
577 | logIgnored();
578 | }
579 | }
580 |
581 | if (!isDoubleFiredEvent()){
582 | if (isSizeChangeDetected()){
583 | recordTrigger();
584 | lockTrigger();
585 | resizeIFrame();
586 | } else {
587 | checkDownSizing();
588 | }
589 | } else {
590 | log('Trigger event cancelled: '+triggerEvent);
591 | }
592 | }
593 |
594 | function lockTrigger(){
595 | if (!triggerLocked){
596 | triggerLocked = true;
597 | log('Trigger event lock on');
598 | }
599 | clearTimeout(triggerLockedTimer);
600 | triggerLockedTimer = setTimeout(function(){
601 | triggerLocked = false;
602 | log('Trigger event lock off');
603 | log('--');
604 | },eventCancelTimer);
605 | }
606 |
607 | function triggerReset(triggerEvent){
608 | height = getHeight[heightCalcMode]();
609 | width = getWidth();
610 |
611 | sendMsg(height,width,triggerEvent);
612 | }
613 |
614 | function resetIFrame(triggerEventDesc){
615 | var hcm = heightCalcMode;
616 | heightCalcMode = heightCalcModeDefault;
617 |
618 | log('Reset trigger event: ' + triggerEventDesc);
619 | lockTrigger();
620 | triggerReset('reset');
621 |
622 | heightCalcMode = hcm;
623 | }
624 |
625 | function sendMsg(height,width,triggerEvent,msg,targetOrigin){
626 | function setTargetOrigin(){
627 | if (undefined === targetOrigin){
628 | targetOrigin = targetOriginDefault;
629 | } else {
630 | log('Message targetOrigin: '+targetOrigin);
631 | }
632 | }
633 |
634 | function sendToParent(){
635 | var
636 | size = height + ':' + width,
637 | message = myID + ':' + size + ':' + triggerEvent + (undefined !== msg ? ':' + msg : '');
638 |
639 | log('Sending message to host page (' + message + ')');
640 | target.postMessage( msgID + message, targetOrigin);
641 | }
642 |
643 | setTargetOrigin();
644 | sendToParent();
645 | }
646 |
647 | function receiver(event) {
648 | function isMessageForUs(){
649 | return msgID === (''+event.data).substr(0,msgIdLen); //''+ Protects against non-string messages
650 | }
651 |
652 | function initFromParent(){
653 | initMsg = event.data;
654 | target = event.source;
655 |
656 | init();
657 | firstRun = false;
658 | setTimeout(function(){ initLock = false;},eventCancelTimer);
659 | }
660 |
661 | function resetFromParent(){
662 | if (!initLock){
663 | log('Page size reset by host page');
664 | triggerReset('resetPage');
665 | } else {
666 | log('Page reset ignored by init');
667 | }
668 | }
669 |
670 | function resizeFromParent(){
671 | sendSize('resizeParent','Parent window resized');
672 | }
673 |
674 | function getMessageType(){
675 | return event.data.split(']')[1];
676 | }
677 |
678 | function isMiddleTier(){
679 | return ('iFrameResize' in window);
680 | }
681 |
682 | function isInitMsg(){
683 | //test if this message is from a child below us. This is an ugly test, however, updating
684 | //the message format would break backwards compatibility.
685 | return event.data.split(':')[2] in {'true':1,'false':1};
686 | }
687 |
688 | if (isMessageForUs()){
689 | if (firstRun === false) {
690 | if ('reset' === getMessageType()){
691 | resetFromParent();
692 | } else if ('resize' === getMessageType()){
693 | resizeFromParent();
694 | } else if (event.data !== initMsg && !isMiddleTier()){
695 | warn('Unexpected message ('+event.data+')');
696 | }
697 | } else if (isInitMsg()) {
698 | initFromParent();
699 | } else {
700 | warn('Received message of type ('+getMessageType()+') before initialization.');
701 | }
702 | }
703 | }
704 |
705 | addEventListener(window, 'message', receiver);
706 |
707 | })();
--------------------------------------------------------------------------------
/docs/js/iframe.js:
--------------------------------------------------------------------------------
1 | /*
2 | * File: iframeResizer.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 | * Contributor: Reed Dadoune - reed@dadoune.com
8 | */
9 | ;(function() {
10 | 'use strict';
11 |
12 | var
13 | count = 0,
14 | firstRun = true,
15 | logEnabled = false,
16 | msgHeader = 'message',
17 | msgHeaderLen = msgHeader.length,
18 | msgId = '[iFrameSizer]', //Must match iframe msg ID
19 | msgIdLen = msgId.length,
20 | pagePosition = null,
21 | requestAnimationFrame = window.requestAnimationFrame,
22 | resetRequiredMethods = {max:1,scroll:1,bodyScroll:1,documentElementScroll:1},
23 | settings = {},
24 | timer = null,
25 |
26 | defaults = {
27 | autoResize : true,
28 | bodyBackground : null,
29 | bodyMargin : null,
30 | bodyMarginV1 : 8,
31 | bodyPadding : null,
32 | checkOrigin : true,
33 | enableInPageLinks : false,
34 | enablePublicMethods : false,
35 | heightCalculationMethod : 'offset',
36 | interval : 32,
37 | log : false,
38 | maxHeight : Infinity,
39 | maxWidth : Infinity,
40 | minHeight : 0,
41 | minWidth : 0,
42 | resizeFrom : 'parent',
43 | scrolling : false,
44 | sizeHeight : true,
45 | sizeWidth : false,
46 | tolerance : 0,
47 | closedCallback : function(){},
48 | initCallback : function(){},
49 | messageCallback : function(){},
50 | resizedCallback : function(){},
51 | scrollCallback : function(){return true;}
52 | };
53 |
54 | function addEventListener(obj,evt,func){
55 | if ('addEventListener' in window){
56 | obj.addEventListener(evt,func, false);
57 | } else if ('attachEvent' in window){//IE
58 | obj.attachEvent('on'+evt,func);
59 | }
60 | }
61 |
62 | function setupRequestAnimationFrame(){
63 | var
64 | vendors = ['moz', 'webkit', 'o', 'ms'],
65 | x;
66 |
67 | // Remove vendor prefixing if prefixed and break early if not
68 | for (x = 0; x < vendors.length && !requestAnimationFrame; x += 1) {
69 | requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
70 | }
71 |
72 | if (!(requestAnimationFrame)){
73 | log(' RequestAnimationFrame not supported');
74 | }
75 | }
76 |
77 | function getMyID(){
78 | var retStr = 'Host page';
79 |
80 | if (window.top!==window.self){
81 | if (window.parentIFrame){
82 | retStr = window.parentIFrame.getId();
83 | } else {
84 | retStr = 'Nested host page';
85 | }
86 | }
87 |
88 | return retStr;
89 | }
90 |
91 | function formatLogMsg(msg){
92 | return msgId + '[' + getMyID() + ']' + msg;
93 | }
94 |
95 | function log(msg){
96 | if (logEnabled && ('object' === typeof window.console)){
97 | console.log(formatLogMsg(msg));
98 | }
99 | }
100 |
101 | function warn(msg){
102 | if ('object' === typeof window.console){
103 | console.warn(formatLogMsg(msg));
104 | }
105 | }
106 |
107 | function iFrameListener(event){
108 | function resizeIFrame(){
109 | function resize(){
110 | setSize(messageData);
111 | setPagePosition();
112 | settings[iframeID].resizedCallback(messageData);
113 | }
114 |
115 | ensureInRange('Height');
116 | ensureInRange('Width');
117 |
118 | syncResize(resize,messageData,'resetPage');
119 | }
120 |
121 | function closeIFrame(iframe){
122 | var iframeID = iframe.id;
123 |
124 | log(' Removing iFrame: '+iframeID);
125 | iframe.parentNode.removeChild(iframe);
126 | settings[iframeID].closedCallback(iframeID);
127 | delete settings[iframeID];
128 | log(' --');
129 | }
130 |
131 | function processMsg(){
132 | var data = msg.substr(msgIdLen).split(':');
133 |
134 | return {
135 | iframe: document.getElementById(data[0]),
136 | id: data[0],
137 | height: data[1],
138 | width: data[2],
139 | type: data[3]
140 | };
141 | }
142 |
143 | function ensureInRange(Dimension){
144 | var
145 | max = Number(settings[iframeID]['max'+Dimension]),
146 | min = Number(settings[iframeID]['min'+Dimension]),
147 | dimension = Dimension.toLowerCase(),
148 | size = Number(messageData[dimension]);
149 |
150 | if (min>max){
151 | throw new Error('Value for min'+Dimension+' can not be greater than max'+Dimension);
152 | }
153 |
154 | log(' Checking '+dimension+' is in range '+min+'-'+max);
155 |
156 | if (sizemax) {
162 | size=max;
163 | log(' Set '+dimension+' to max value');
164 | }
165 |
166 | messageData[dimension]=''+size;
167 | }
168 |
169 |
170 | function isMessageFromIFrame(){
171 | function checkAllowedOrigin(){
172 | function checkList(){
173 | log(' Checking connection is from allowed list of origins: ' + checkOrigin);
174 | var i;
175 | for (i = 0; i < checkOrigin.length; i++) {
176 | if (checkOrigin[i] === origin) {
177 | return true;
178 | }
179 | }
180 | return false;
181 | }
182 |
183 | function checkSingle(){
184 | log(' Checking connection is from: '+remoteHost);
185 | return origin == remoteHost;
186 | }
187 |
188 | return checkOrigin.constructor === Array ? checkList() : checkSingle();
189 | }
190 |
191 | var
192 | origin = event.origin,
193 | checkOrigin = settings[iframeID].checkOrigin,
194 | remoteHost = messageData.iframe.src.split('/').slice(0,3).join('/');
195 |
196 | if (checkOrigin) {
197 | if ((''+origin !== 'null') && !checkAllowedOrigin()) {
198 | throw new Error(
199 | 'Unexpected message received from: ' + origin +
200 | ' for ' + messageData.iframe.id +
201 | '. Message was: ' + event.data +
202 | '. This error can be disabled by setting the checkOrigin: false option or by providing of array of trusted domains.'
203 | );
204 | }
205 | }
206 |
207 | return true;
208 | }
209 |
210 | function isMessageForUs(){
211 | return msgId === ('' + msg).substr(0,msgIdLen); //''+Protects against non-string msg
212 | }
213 |
214 | function isMessageFromMetaParent(){
215 | //test if this message is from a parent above us. This is an ugly test, however, updating
216 | //the message format would break backwards compatibility.
217 | var retCode = messageData.type in {'true':1,'false':1,'undefined':1};
218 |
219 | if (retCode){
220 | log(' Ignoring init message from meta parent page');
221 | }
222 |
223 | return retCode;
224 | }
225 |
226 | function getMsgBody(offset){
227 | return msg.substr(msg.indexOf(':')+msgHeaderLen+offset);
228 | }
229 |
230 | function forwardMsgFromIFrame(msgBody){
231 | log(' MessageCallback passed: {iframe: '+ messageData.iframe.id + ', message: ' + msgBody + '}');
232 | settings[iframeID].messageCallback({
233 | iframe: messageData.iframe,
234 | message: JSON.parse(msgBody)
235 | });
236 | log(' --');
237 | }
238 |
239 | function checkIFrameExists(){
240 | if (null === messageData.iframe) {
241 | warn(' IFrame ('+messageData.id+') not found');
242 | return false;
243 | }
244 | return true;
245 | }
246 |
247 | function getElementPosition(target){
248 | var
249 | iFramePosition = target.getBoundingClientRect();
250 |
251 | getPagePosition();
252 |
253 | return {
254 | x: parseInt(iFramePosition.left, 10) + parseInt(pagePosition.x, 10),
255 | y: parseInt(iFramePosition.top, 10) + parseInt(pagePosition.y, 10)
256 | };
257 | }
258 |
259 | function scrollRequestFromChild(addOffset){
260 | function reposition(){
261 | pagePosition = newPosition;
262 |
263 | scrollTo();
264 |
265 | log(' --');
266 | }
267 |
268 | function calcOffset(){
269 | return {
270 | x: Number(messageData.width) + offset.x,
271 | y: Number(messageData.height) + offset.y
272 | };
273 | }
274 |
275 | var
276 | offset = addOffset ? getElementPosition(messageData.iframe) : {x:0,y:0},
277 | newPosition = calcOffset();
278 |
279 | log(' Reposition requested from iFrame (offset x:'+offset.x+' y:'+offset.y+')');
280 |
281 | if(window.top!==window.self){
282 | if (window.parentIFrame){
283 | if (addOffset){
284 | parentIFrame.scrollToOffset(newPosition.x,newPosition.y);
285 | } else {
286 | parentIFrame.scrollTo(messageData.width,messageData.height);
287 | }
288 | } else {
289 | warn(' Unable to scroll to requested position, window.parentIFrame not found');
290 | }
291 | } else {
292 | reposition();
293 | }
294 |
295 | }
296 |
297 | function scrollTo(){
298 | if (false !== settings[iframeID].scrollCallback(pagePosition)){
299 | setPagePosition();
300 | }
301 | }
302 |
303 | function findTarget(location){
304 | var hash = location.split("#")[1] || "";
305 | var hashData = decodeURIComponent(hash);
306 |
307 | function jumpToTarget(target){
308 | var jumpPosition = getElementPosition(target);
309 |
310 | log(' Moving to in page link (#'+hash+') at x: '+jumpPosition.x+' y: '+jumpPosition.y);
311 | pagePosition = {
312 | x: jumpPosition.x,
313 | y: jumpPosition.y
314 | };
315 |
316 | scrollTo();
317 | log(' --');
318 | }
319 |
320 | var target = document.getElementById(hashData) || document.getElementsByName(hashData)[0];
321 |
322 | if(window.top!==window.self){
323 | if (window.parentIFrame){
324 | parentIFrame.moveToAnchor(hash);
325 | } else {
326 | log(' In page link #'+hash+' not found and window.parentIFrame not found');
327 | }
328 | } else if (target){
329 | jumpToTarget(target);
330 | } else {
331 | log(' In page link #'+hash+' not found');
332 | }
333 | }
334 |
335 | function actionMsg(){
336 | switch(messageData.type){
337 | case 'close':
338 | closeIFrame(messageData.iframe);
339 | break;
340 | case 'message':
341 | forwardMsgFromIFrame(getMsgBody(6));
342 | break;
343 | case 'scrollTo':
344 | scrollRequestFromChild(false);
345 | break;
346 | case 'scrollToOffset':
347 | scrollRequestFromChild(true);
348 | break;
349 | case 'inPageLink':
350 | findTarget(getMsgBody(9));
351 | break;
352 | case 'reset':
353 | resetIFrame(messageData);
354 | break;
355 | case 'init':
356 | resizeIFrame();
357 | settings[iframeID].initCallback(messageData.iframe);
358 | break;
359 | default:
360 | resizeIFrame();
361 | }
362 | }
363 |
364 | function hasSettings(iframeID){
365 | var retBool = true;
366 |
367 | if (!settings[iframeID]){
368 | retBool = false;
369 | warn(messageData.type + ' No settings for ' + iframeID + '. Message was: ' + msg);
370 | }
371 |
372 | return retBool;
373 | }
374 |
375 | var
376 | msg = event.data,
377 | messageData = {},
378 | iframeID = null;
379 |
380 | if (isMessageForUs()){
381 | messageData = processMsg();
382 | iframeID = messageData.id;
383 |
384 | if (!isMessageFromMetaParent() && hasSettings(iframeID)){
385 | logEnabled = settings[iframeID].log;
386 | log(' Received: '+msg);
387 |
388 | if ( checkIFrameExists() && isMessageFromIFrame() ){
389 | actionMsg();
390 | firstRun = false;
391 | }
392 | }
393 | }
394 | }
395 |
396 |
397 | function getPagePosition (){
398 | if(null === pagePosition){
399 | pagePosition = {
400 | x: (window.pageXOffset !== undefined) ? window.pageXOffset : document.documentElement.scrollLeft,
401 | y: (window.pageYOffset !== undefined) ? window.pageYOffset : document.documentElement.scrollTop
402 | };
403 | log(' Get page position: '+pagePosition.x+','+pagePosition.y);
404 | }
405 | }
406 |
407 | function setPagePosition(){
408 | if(null !== pagePosition){
409 | window.scrollTo(pagePosition.x,pagePosition.y);
410 | log(' Set page position: '+pagePosition.x+','+pagePosition.y);
411 | pagePosition = null;
412 | }
413 | }
414 |
415 | function resetIFrame(messageData){
416 | function reset(){
417 | setSize(messageData);
418 | trigger('reset','reset',messageData.iframe,messageData.id);
419 | }
420 |
421 | log(' Size reset requested by '+('init'===messageData.type?'host page':'iFrame'));
422 | getPagePosition();
423 | syncResize(reset,messageData,'init');
424 | }
425 |
426 | function setSize(messageData){
427 | function setDimension(dimension){
428 | messageData.iframe.style[dimension] = messageData[dimension] + 'px';
429 | log(
430 | ' IFrame (' + iframeID +
431 | ') ' + dimension +
432 | ' set to ' + messageData[dimension] + 'px'
433 | );
434 | }
435 | var iframeID = messageData.iframe.id;
436 | if( settings[iframeID].sizeHeight) { setDimension('height'); }
437 | if( settings[iframeID].sizeWidth ) { setDimension('width'); }
438 | }
439 |
440 | function syncResize(func,messageData,doNotSync){
441 | if(doNotSync!==messageData.type && requestAnimationFrame){
442 | log(' Requesting animation frame');
443 | requestAnimationFrame(func);
444 | } else {
445 | func();
446 | }
447 | }
448 |
449 | function trigger(calleeMsg,msg,iframe,id){
450 | if(iframe && iframe.contentWindow){
451 | log('[' + calleeMsg + '] Sending msg to iframe ('+msg+')');
452 | iframe.contentWindow.postMessage( msgId + msg, '*' );
453 | } else {
454 | warn('[' + calleeMsg + '] IFrame not found');
455 | if(settings[id]) delete settings[id];
456 | }
457 | }
458 |
459 |
460 | function setupIFrame(options){
461 | function setLimits(){
462 | function addStyle(style){
463 | if ((Infinity !== settings[iframeID][style]) && (0 !== settings[iframeID][style])){
464 | iframe.style[style] = settings[iframeID][style] + 'px';
465 | log(' Set '+style+' = '+settings[iframeID][style]+'px');
466 | }
467 | }
468 |
469 | addStyle('maxHeight');
470 | addStyle('minHeight');
471 | addStyle('maxWidth');
472 | addStyle('minWidth');
473 | }
474 |
475 | function ensureHasId(iframeID){
476 | if (''===iframeID){
477 | iframe.id = iframeID = 'iFrameResizer' + count++;
478 | logEnabled = (options || {}).log;
479 | log(' Added missing iframe ID: '+ iframeID +' (' + iframe.src + ')');
480 | }
481 |
482 | return iframeID;
483 | }
484 |
485 | function setScrolling(){
486 | log(' IFrame scrolling ' + (settings[iframeID].scrolling ? 'enabled' : 'disabled') + ' for ' + iframeID);
487 | iframe.style.overflow = false === settings[iframeID].scrolling ? 'hidden' : 'auto';
488 | iframe.scrolling = false === settings[iframeID].scrolling ? 'no' : 'yes';
489 | }
490 |
491 | //The V1 iFrame script expects an int, where as in V2 expects a CSS
492 | //string value such as '1px 3em', so if we have an int for V2, set V1=V2
493 | //and then convert V2 to a string PX value.
494 | function setupBodyMarginValues(){
495 | if (('number'===typeof(settings[iframeID].bodyMargin)) || ('0'===settings[iframeID].bodyMargin)){
496 | settings[iframeID].bodyMarginV1 = settings[iframeID].bodyMargin;
497 | settings[iframeID].bodyMargin = '' + settings[iframeID].bodyMargin + 'px';
498 | }
499 | }
500 |
501 | function createOutgoingMsg(){
502 | return iframeID +
503 | ':' + settings[iframeID].bodyMarginV1 +
504 | ':' + settings[iframeID].sizeWidth +
505 | ':' + settings[iframeID].log +
506 | ':' + settings[iframeID].interval +
507 | ':' + settings[iframeID].enablePublicMethods +
508 | ':' + settings[iframeID].autoResize +
509 | ':' + settings[iframeID].bodyMargin +
510 | ':' + settings[iframeID].heightCalculationMethod +
511 | ':' + settings[iframeID].bodyBackground +
512 | ':' + settings[iframeID].bodyPadding +
513 | ':' + settings[iframeID].tolerance +
514 | ':' + settings[iframeID].enableInPageLinks +
515 | ':' + settings[iframeID].resizeFrom;
516 | }
517 |
518 | function init(msg){
519 | //We have to call trigger twice, as we can not be sure if all
520 | //iframes have completed loading when this code runs. The
521 | //event listener also catches the page changing in the iFrame.
522 | addEventListener(iframe,'load',function(){
523 | var fr = firstRun; // Reduce scope of var to function, because IE8's JS execution
524 | // context stack is borked and this value gets externally
525 | // changed midway through running this function.
526 | trigger('iFrame.onload',msg,iframe);
527 | if (!fr && settings[iframeID].heightCalculationMethod in resetRequiredMethods){
528 | resetIFrame({
529 | iframe:iframe,
530 | height:0,
531 | width:0,
532 | type:'init'
533 | });
534 | }
535 | });
536 | trigger('init',msg,iframe);
537 | }
538 |
539 | function checkOptions(options){
540 | if ('object' !== typeof options){
541 | throw new TypeError('Options is not an object.');
542 | }
543 | }
544 |
545 | function processOptions(options){
546 | options = options || {};
547 | settings[iframeID] = {};
548 |
549 | checkOptions(options);
550 |
551 | for (var option in defaults) {
552 | if (defaults.hasOwnProperty(option)){
553 | settings[iframeID][option] = options.hasOwnProperty(option) ? options[option] : defaults[option];
554 | }
555 | }
556 |
557 | logEnabled = settings[iframeID].log;
558 | }
559 |
560 | var
561 | /*jshint validthis:true */
562 | iframe = this,
563 | iframeID = ensureHasId(iframe.id);
564 |
565 | processOptions(options);
566 | setScrolling();
567 | setLimits();
568 | setupBodyMarginValues();
569 | init(createOutgoingMsg());
570 | }
571 |
572 | function throttle(fn,time){
573 | if (null === timer){
574 | timer = setTimeout(function(){
575 | timer = null;
576 | fn();
577 | }, time);
578 | }
579 | }
580 |
581 | function winResize(){
582 | throttle(function(){
583 | for (var iframeId in settings){
584 | if('parent' === settings[iframeId].resizeFrom){
585 | trigger('Window resize','resize',document.getElementById(iframeId),iframeId);
586 | }
587 | }
588 | },66);
589 | }
590 |
591 | function factory(){
592 |
593 | setupRequestAnimationFrame();
594 | addEventListener(window,'message',iFrameListener);
595 | addEventListener(window,'resize', winResize);
596 |
597 | function init(element, options){
598 | if(!element.tagName) {
599 | throw new TypeError('Object is not a valid DOM element');
600 | } else if ('IFRAME' !== element.tagName.toUpperCase()) {
601 | throw new TypeError('Expected