Skip to content

Commit b893b23

Browse files
authored
feat: post component height to parent window [FC-0083] (#36477)
XBlock rendered in an iframe in frontend-app-authoring needs to resize automatically based on component size. Updates xblock iframe template to post message to parent window on size change.
1 parent 11bab7d commit b893b23

2 files changed

Lines changed: 56 additions & 17 deletions

File tree

common/templates/xblock_v2/xblock_iframe.html

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -180,8 +180,8 @@
180180
<!-- The default stylesheet will set the body min-height to 100% (a common strategy to allow for background
181181
images to fill the viewport), but this has the undesireable side-effect of causing an infinite loop via the onResize
182182
event listeners below, in certain situations. Resetting it to the default "auto" skirts the problem.-->
183-
<body style="min-height: auto; background-color: white;" class="view-container">
184-
<div class="wrapper xblock-iframe-content">
183+
<body style="background-color: white;" class="view-container">
184+
<div id="content" class="wrapper xblock-iframe-content">
185185
<!-- fragment body -->
186186
{{ fragment.body_html | safe }}
187187
<!-- fragment foot -->
@@ -360,7 +360,7 @@
360360
error_message_div.html('Error: '+response.message);
361361
error_message_div.css('display', 'block');
362362
}
363-
});
363+
});
364364
});
365365
});
366366
}
@@ -437,21 +437,55 @@
437437
const rootNode = document.querySelector('.xblock, .xblock-v1'); // will always return the first matching element
438438
initializeXBlockAndChildren(rootNode, () => {
439439
});
440+
(function() {
441+
// If this view is rendered in an iframe within the authoring microfrontend app
442+
// it will report the height of its contents to the parent window when the
443+
// document loads, window resizes, or DOM mutates.
444+
if (window !== window.parent) {
445+
var lastHeight = window.parent[0].offsetHeight;
446+
var lastWidth = window.parent[0].offsetWidth;
440447

441-
let lastHeight = -1;
442-
function checkFrameHeight() {
443-
const newHeight = document.documentElement.scrollHeight;
444-
if (newHeight !== lastHeight) {
445-
lastHeight = newHeight;
448+
function dispatchResizeMessage(event) {
449+
// Note: event is actually an Array of MutationRecord objects when fired from the MutationObserver
450+
var newHeight = rootNode.scrollHeight;
451+
var newWidth = rootNode.offsetWidth;
452+
453+
window.parent.postMessage(
454+
{
455+
type: 'plugin.resize',
456+
payload: {
457+
width: newWidth,
458+
height: newHeight,
459+
}
460+
}, document.referrer
461+
);
462+
463+
lastHeight = newHeight;
464+
lastWidth = newWidth;
465+
466+
// Within the authoring microfrontend the iframe resizes to match the
467+
// height of this document and it should never scroll. It does scroll
468+
// ocassionally when javascript is used to focus elements on the page
469+
// before the parent iframe has been resized to match the content
470+
// height. This window.scrollTo is an attempt to keep the content at the
471+
// top of the page.
472+
window.scrollTo(0, 0);
473+
}
474+
475+
// Create an observer instance linked to the callback function
476+
const observer = new MutationObserver(dispatchResizeMessage);
477+
478+
// Start observing the target node for configured mutations
479+
observer.observe(rootNode, { attributes: true, childList: true, subtree: true });
480+
481+
const resizeObserver = new ResizeObserver(dispatchResizeMessage);
482+
resizeObserver.observe(rootNode);
446483
}
447-
}
448-
// Check the size whenever the DOM changes:
449-
new MutationObserver(checkFrameHeight).observe(document.body, { attributes: true, childList: true, subtree: true });
450-
// And whenever the IFrame is resized
451-
window.addEventListener('resize', checkFrameHeight);
484+
}());
452485
}
453486

454487
window.addEventListener('load', blockFrameJS);
488+
455489
</script>
456490
</body>
457491
</html>

openedx/core/djangoapps/content_libraries/api/libraries.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -211,12 +211,17 @@ class LibraryXBlockMetadata(PublishableItem):
211211
Class that represents the metadata about an XBlock in a content library.
212212
"""
213213
usage_key: LibraryUsageLocatorV2
214+
# TODO: move tags_count to LibraryItem as all objects under a library can be tagged.
215+
tags_count: int = 0
214216

215217
@classmethod
216218
def from_component(cls, library_key, component, associated_collections=None):
217219
"""
218220
Construct a LibraryXBlockMetadata from a Component object.
219221
"""
222+
# Import content_tagging.api here to avoid circular imports
223+
from openedx.core.djangoapps.content_tagging.api import get_object_tag_counts
224+
220225
last_publish_log = component.versioning.last_publish_log
221226

222227
published_by = None
@@ -227,12 +232,11 @@ def from_component(cls, library_key, component, associated_collections=None):
227232
published = component.versioning.published
228233
last_draft_created = draft.created if draft else None
229234
last_draft_created_by = draft.publishable_entity_version.created_by if draft else None
235+
usage_key = library_component_usage_key(library_key, component)
236+
tags = get_object_tag_counts(str(usage_key), count_implicit=True)
230237

231238
return cls(
232-
usage_key=library_component_usage_key(
233-
library_key,
234-
component,
235-
),
239+
usage_key=usage_key,
236240
display_name=draft.title,
237241
created=component.created,
238242
modified=draft.created,
@@ -245,6 +249,7 @@ def from_component(cls, library_key, component, associated_collections=None):
245249
has_unpublished_changes=component.versioning.has_unpublished_changes,
246250
collections=associated_collections or [],
247251
can_stand_alone=component.publishable_entity.can_stand_alone,
252+
tags_count=tags.get(str(usage_key), 0),
248253
)
249254

250255

0 commit comments

Comments
 (0)