├── .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 |
36 |

What Class Classification

37 |
38 | 39 |
40 | wr786 41 |
42 | 43 |
44 | 点击获取脚本 45 |
46 | 47 | 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