/* * Copyright (c) 2020, University of Oxford */ import React from 'react' import PropTypes from 'prop-types' /** * This is just an div that covers the whole of the iframe. When the surrounding * page is scrolled the div is moved to the new location. You should just have * a single one of these components in an application and all components should * use it through the id. */ class FixedIFrame extends React.Component { static propTypes = { id: PropTypes.string } static defaultProps = { id: 'lti-fixed-content' } state = { height: '0px', top: '0px' } /** * Registers to get scroll events from Canvas. There's no way to unregister * from scroll events and we don't want multiple registrations so we ensure * that we only register once. */ ltiScrollEvents = () => { // We only want to do this once. if (!this.registeredScroll) { window.parent.postMessage(JSON.stringify({ subject: 'lti.enableScrollEvents' }), '*') this.registeredScroll = true } } componentDidMount() { window.addEventListener('resize', this.resizeListener) window.addEventListener('message', this.messageListener) this.fetchWindowSize() this.ltiScrollEvents() } componentWillUnmount() { window.removeEventListener('message', this.resizeListener) window.removeEventListener('resize', this.fetchWindowSize) } resizeListener = () => this.fetchWindowSize() fetchWindowSize = () => { // When running outside of the iframe don't send the message as it goes to ourselves if (window.parent !== window) { window.parent.postMessage(JSON.stringify({ subject: 'lti.fetchWindowSize' }), '*') } } messageListener = e => e.data && this.messageHandler(e) messageHandler = (event) => { let message try { message = typeof event.data === 'string' ? JSON.parse(event.data) : event.data } catch (err) { // unparseable message may not be meant for our handlers return } if (message.subject === 'lti.fetchWindowSize') { this.message = message this.scrollHandler() } else if (message.subject === 'lti.scroll') { this.message = { ...this.message, ...message } this.scrollHandler() } } scrollHandler = () => { const message = this.message // We might get a scroll event before we know the size of the window if (message.offset) { // Math.max to stop it overflowing over the beginning of the document. const top = Math.max(message.scrollY - message.offset.top, 0) // Math.min to stop it overflowing the bottom and generating a second scroll bar. const bottom = Math.min(message.height + message.scrollY - message.offset.top, document.documentElement.scrollHeight) this.setState({ top: top + 'px', height: (bottom - top) + 'px' }) } } render() { return
} } export default FixedIFrame