Files
edx-platform/cms/templates/content_libraries/xblock_iframe.html

296 lines
13 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<!-- Open links in a new tab, not this iframe -->
<base target="_blank">
<meta charset="UTF-8">
<!-- gettext & XBlock JS i18n code -->
<script type="text/javascript" src="{{ lms_root_url }}/static/js/i18n/en/djangojs.js"></script>
<!-- Most XBlocks require jQuery: -->
<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
<!-- The Video XBlock requires "ajaxWithPrefix" -->
<script type="text/javascript">
$.postWithPrefix = $.post;
$.getWithPrefix = $.get;
$.ajaxWithPrefix = $.ajax;
</script>
<!-- The Video XBlock requires "Slider" from jQuery-UI: -->
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
<!-- The video XBlock depends on Underscore.JS -->
<script type="text/javascript" src="{{ lms_root_url }}/static/common/js/vendor/underscore.js"></script>
<!-- The video XBlock depends on jquery-cookie -->
<script type="text/javascript" src="{{ lms_root_url }}/static/js/vendor/jquery.cookie.js"></script>
<!--The Video XBlock has an undeclared dependency on 'Logger' -->
<script>
window.Logger = { log: function() { } };
</script>
<!-- Builtin XBlock types depend on RequireJS -->
<script type="text/javascript" src="{{ lms_root_url }}/static/common/js/vendor/require.js"></script>
<script type="text/javascript" src="{{ lms_root_url }}/static/js/RequireJS-namespace-undefine.js"></script>
<script>
// The minimal RequireJS configuration required for common LMS building XBlock types to work:
(function (require, define) {
require.config({
baseUrl: "{{ lms_root_url }}/static/",
paths: {
accessibility: 'js/src/accessibility_tools',
draggabilly: 'js/vendor/draggabilly',
hls: 'common/js/vendor/hls',
moment: 'common/js/vendor/moment-with-locales',
HtmlUtils: 'edx-ui-toolkit/js/utils/html-utils',
},
});
define('gettext', [], function() { return window.gettext; });
define('jquery', [], function() { return window.jQuery; });
define('jquery-migrate', [], function() { return window.jQuery; });
define('underscore', [], function() { return window._; });
}).call(this, require || RequireJS.require, define || RequireJS.define);
</script>
<!-- edX HTML Utils requires GlobalLoader -->
<script type="text/javascript" src="{{ lms_root_url }}/static/edx-ui-toolkit/js/utils/global-loader.js"></script>
<script>
// The video XBlock has an undeclared dependency on edX HTML Utils
RequireJS.require(['HtmlUtils'], function (HtmlUtils) {
window.edx.HtmlUtils = HtmlUtils;
// The problem XBlock depends on window.SR, though 'accessibility_tools' has an undeclared dependency on HtmlUtils:
RequireJS.require(['accessibility']);
});
RequireJS.require(['edx-ui-toolkit/js/utils/string-utils'], function (StringUtils) {
window.edx.StringUtils = StringUtils;
});
</script>
<!--
commons.js: this file produced by webpack contains many shared chunks of code.
By including this, you have only to also import any of the smaller entrypoint
files (defined in webpack.common.config.js) to get that entry point and all
of its dependencies.
-->
<script type="text/javascript" src="{{ lms_root_url }}/static/bundles/commons.js"></script>
<!-- The video XBlock (and perhaps others?) expect this global: -->
<script>
window.onTouchBasedDevice = function() { return navigator.userAgent.match(/iPhone|iPod|iPad|Android/i); };
</script>
<!-- At least one XBlock (drag and drop v2) expects Font Awesome -->
<link rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
<!-- Capa Problem Editing requires CodeMirror -->
<link rel="stylesheet" href="{{ lms_root_url }}/static/js/vendor/CodeMirror/codemirror.css">
<!-- Built-in XBlocks (and some plugins) depends on LMS CSS -->
<link rel="stylesheet" href="{{ lms_root_url }}/static/css/lms-course.css">
<!-- Configure and load MathJax -->
<script type="text/x-mathjax-config">
MathJax.Hub.Config({
tex2jax: {
inlineMath: [
["\\(","\\)"],
['[mathjaxinline]','[/mathjaxinline]']
],
displayMath: [
["\\[","\\]"],
['[mathjax]','[/mathjax]']
]
}
});
</script>
<script type="text/x-mathjax-config">
MathJax.Hub.signal.Interest(function(message) {
if(message[0] === "End Math") {
set_mathjax_display_div_settings();
}
});
function set_mathjax_display_div_settings() {
$('.MathJax_Display').each(function( index ) {
this.setAttribute('tabindex', '0');
this.setAttribute('aria-live', 'off');
this.removeAttribute('role');
this.removeAttribute('aria-readonly');
});
}
</script>
<script type="text/javascript">
// Activating Mathjax accessibility files
window.MathJax = {
menuSettings: {
collapsible: true,
autocollapse: false,
explorer: true
}
};
</script>
<!-- This must appear after all mathjax-config blocks, so it is after the imports from the other templates.
It can't be run through static.url because MathJax uses crazy url introspection to do lazy loading of
MathJax extension libraries -->
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/mathjax@2.7.5/MathJax.js?config=TeX-MML-AM_SVG"></script>
<!-- fragment head -->
{{ fragment.head_html | safe }}
</head>
<body>
<!-- fragment body -->
{{ fragment.body_html | safe }}
<!-- fragment foot -->
{{ fragment.foot_html | safe }}
<script>
/**
* Map of all URL handlers for this block and its children, keyed by usage
* key.
*/
{% comment %}
This variable is expected to be a valid JSON, which will be translated
directly into a javascript object.
{% endcomment %}
HANDLER_URL_MAP = {{ handler_urls_json | safe }};
/**
* The JavaScript code which runs inside our IFrame and is responsible
* for communicating with the parent window.
*
* This cannot use any imported functions because it runs in the IFrame,
* not in our app webpack bundle.
*/
function blockFrameJS() {
const CHILDREN_KEY = '_jsrt_xb_children'; // JavaScript RunTime XBlock children
const USAGE_ID_KEY = '_jsrt_xb_usage_id';
const HANDLER_URL = '_jsrt_xb_handler_url';
const uniqueKeyPrefix = `k${+Date.now()}-${Math.floor(Math.random() * 1e10)}-`;
let messageCount = 0;
/**
* The JavaScript runtime for any XBlock in the IFrame
*/
const runtime = {
/**
* An obscure and little-used API that retrieves a particular
* XBlock child using its 'data-name' attribute
* @param block The root DIV element of the XBlock calling this method
* @param childName The value of the 'data-name' attribute of the root
* DIV element of the XBlock child in question.
*/
childMap: (block, childName) => runtime.children(block).find((child) => child.element.getAttribute('data-name') === childName),
children: (block) => block[CHILDREN_KEY],
/**
* Get the URL for the specified handler. This method must be synchronous, so
* cannot make HTTP requests.
*/
handlerUrl: (block, handlerName, suffix, query) => {
let url = block[HANDLER_URL].replace('handler_name', handlerName);
if (suffix) {
url += `${suffix}/`;
}
if (query) {
url += `?${query}`;
}
return url;
},
};
/**
* Initialize an XBlock. This function should only be called by initializeXBlockAndChildren
* because it assumes that function has already run.
*/
function initializeXBlock(element, callback) {
const usageId = element[USAGE_ID_KEY];
// Check if the XBlock has an initialization function:
const initFunctionName = element.getAttribute('data-init');
if (initFunctionName !== null) {
// Since this block has an init function, it may need to call handlers:
element[HANDLER_URL] = HANDLER_URL_MAP[usageId];
// Now proceed with initializing the block's JavaScript:
const InitFunction = (window)[initFunctionName];
// Does the XBlock HTML contain arguments to pass to the InitFunction?
let data = {};
[].forEach.call(element.children, (childNode) => {
// The newer/pure/Blockstore runtime uses 'xblock_json_init_args'
// while the LMS runtime uses 'xblock-json-init-args'.
if (
childNode.matches('script.xblock_json_init_args')
|| childNode.matches('script.xblock-json-init-args')
) {
data = JSON.parse(childNode.textContent);
}
});
// An unfortunate inconsistency is that the old Studio runtime used
// to pass 'element' as a jQuery-wrapped DOM element, whereas the LMS
// runtime used to pass 'element' as the pure DOM node. In order not to
// break backwards compatibility, we would need to maintain that.
// However, this is currently disabled as it causes issues (need to
// modify the runtime methods like handlerUrl too), and we decided not
// to maintain support for legacy studio_view in this runtime.
// const isStudioView = element.className.indexOf('studio_view') !== -1;
// const passElement = isStudioView && (window as any).$ ? (window as any).$(element) : element;
const blockJS = new InitFunction(runtime, element, data) || {};
blockJS.element = element;
callback(blockJS);
} else {
const blockJS = { element };
callback(blockJS);
}
}
// Recursively initialize the JavaScript code of each XBlock:
function initializeXBlockAndChildren(element, callback) {
// The newer/pure/Blockstore runtime uses the 'data-usage' attribute, while the LMS uses 'data-usage-id'
const usageId = element.getAttribute('data-usage') || element.getAttribute('data-usage-id');
if (usageId !== null) {
element[USAGE_ID_KEY] = usageId;
} else {
throw new Error('XBlock is missing a usage ID attribute on its root HTML node.');
}
const version = element.getAttribute('data-runtime-version');
if (version != null && version !== '1') {
throw new Error('Unsupported XBlock runtime version requirement.');
}
// Recursively initialize any children first:
// We need to find all div.xblock-v1 children, unless they're grandchilden
// So we build a list of all div.xblock-v1 descendants that aren't descendants
// of an already-found descendant:
const childNodesFound = [];
[].forEach.call(element.querySelectorAll('.xblock, .xblock-v1'), (childNode) => {
if (!childNodesFound.find((el) => el.contains(childNode))) {
childNodesFound.push(childNode);
}
});
// This code is awkward because we can't use promises (IE11 etc.)
let childrenInitialized = -1;
function initNextChild() {
childrenInitialized += 1;
if (childrenInitialized < childNodesFound.length) {
const childNode = childNodesFound[childrenInitialized];
initializeXBlockAndChildren(childNode, initNextChild);
} else {
// All children are initialized:
initializeXBlock(element, callback);
}
}
initNextChild();
}
// Find the root XBlock node.
// The newer/pure/Blockstore runtime uses '.xblock-v1' while the LMS runtime uses '.xblock'.
const rootNode = document.querySelector('.xblock, .xblock-v1'); // will always return the first matching element
initializeXBlockAndChildren(rootNode, () => {
});
let lastHeight = -1;
function checkFrameHeight() {
const newHeight = document.documentElement.scrollHeight;
if (newHeight !== lastHeight) {
lastHeight = newHeight;
}
}
// Check the size whenever the DOM changes:
new MutationObserver(checkFrameHeight).observe(document.body, { attributes: true, childList: true, subtree: true });
// And whenever the IFrame is resized
window.addEventListener('resize', checkFrameHeight);
}
window.addEventListener('load', blockFrameJS);
</script>
</body>
</html>