From 49b843f22054fd6786fea541b98726e64dbe6d01 Mon Sep 17 00:00:00 2001 From: Agrendalath Date: Thu, 23 May 2024 17:04:50 +0200 Subject: [PATCH] fix: receive XBlock visibility from the Learning MFE --- lms/static/completion/js/ViewedEvent.js | 46 +++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/lms/static/completion/js/ViewedEvent.js b/lms/static/completion/js/ViewedEvent.js index 1ad936f8a15..b2b9d5ff5bc 100644 --- a/lms/static/completion/js/ViewedEvent.js +++ b/lms/static/completion/js/ViewedEvent.js @@ -109,7 +109,13 @@ export class ViewedEventTracker { constructor() { this.elementViewings = new Set(); this.handlers = []; - this.registerDomHandlers(); + if (window === window.parent) { + // Preview (legacy LMS frontend). + this.registerDomHandlers(); + } else { + // Learning MFE. + window.addEventListener('message', this.handleVisibilityMessage.bind(this)); + } } /** Add an element to track. */ @@ -121,7 +127,11 @@ export class ViewedEventTracker { (el, event) => this.callHandlers(el, event), ), ); - this.updateVisible(); + // Update visibility status immediately after adding the element (in case it's already visible). + // We don't need this for the Learning MFE because it will send a message once the iframe is loaded. + if (window === window.parent) { + this.updateVisible(); + } } /** Register a new handler to be called when an element has been viewed. */ @@ -177,4 +187,36 @@ export class ViewedEventTracker { handler(el, event); }); } + + /** Handle a unit.visibilityStatus message from the Learning MFE. */ + handleVisibilityMessage(event) { + if (event.data.type === 'unit.visibilityStatus') { + const { topPosition, viewportHeight } = event.data.data; + + this.elementViewings.forEach((elv) => { + const rect = elv.getBoundingRect(); + let visible = false; + + // Convert iframe-relative rect coordinates to be relative to the parent's viewport. + const elTopPosition = rect.top + topPosition; + const elBottomPosition = rect.bottom + topPosition; + + // Check if the element is visible in the parent's viewport. + if (elTopPosition < viewportHeight && elTopPosition >= 0) { + elv.markTopSeen(); + visible = true; + } + if (elBottomPosition <= viewportHeight && elBottomPosition > 0) { + elv.markBottomSeen(); + visible = true; + } + + if (visible) { + elv.handleVisible(); + } else { + elv.handleNotVisible(); + } + }); + } + } }