chore: iframe rendering optimization (#35959)
Iframe reload optimizations for various xblock related actions. Added some improvements related to scrolling to the current xblock. Fixed behavior of the xblock action dropdown list.
This commit is contained in:
@@ -131,10 +131,40 @@ function($, _, Backbone, gettext, BasePage,
|
||||
|
||||
if (this.options.isIframeEmbed) {
|
||||
window.addEventListener('message', (event) => {
|
||||
if (event.data && event.data.type === 'refreshXBlock') {
|
||||
this.render();
|
||||
}
|
||||
});
|
||||
const { data } = event;
|
||||
|
||||
if (!data) return;
|
||||
|
||||
let xblockElement;
|
||||
let xblockWrapper;
|
||||
|
||||
if (data.payload && data.payload.locator) {
|
||||
xblockElement = $(`[data-locator="${data.payload.locator}"]`);
|
||||
xblockWrapper = $("li.studio-xblock-wrapper[data-locator='" + data.payload.locator + "']");
|
||||
} else {
|
||||
xblockWrapper = $();
|
||||
}
|
||||
|
||||
switch (data.type) {
|
||||
case 'refreshXBlock':
|
||||
this.render();
|
||||
break;
|
||||
case 'completeManageXBlockAccess':
|
||||
this.refreshXBlock(xblockElement, false);
|
||||
break;
|
||||
case 'completeXBlockMoving':
|
||||
xblockWrapper.hide();
|
||||
break;
|
||||
case 'rollbackMovedXBlock':
|
||||
xblockWrapper.show();
|
||||
break;
|
||||
case 'addXBlock':
|
||||
this.createComponent(this, xblockElement, data);
|
||||
break;
|
||||
default:
|
||||
console.warn('Unhandled message type:', data.type);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.listenTo(Backbone, 'move:onXBlockMoved', this.onXBlockMoved);
|
||||
@@ -199,6 +229,25 @@ function($, _, Backbone, gettext, BasePage,
|
||||
target.scrollIntoView({ behavior: 'smooth', inline: 'center' });
|
||||
}
|
||||
|
||||
if (self.options.isIframeEmbed) {
|
||||
const scrollOffsetString = localStorage.getItem('modalEditLastYPosition');
|
||||
const scrollOffset = scrollOffsetString ? parseInt(scrollOffsetString, 10) : 0;
|
||||
|
||||
if (scrollOffset) {
|
||||
try {
|
||||
window.parent.postMessage(
|
||||
{
|
||||
type: 'scrollToXBlock',
|
||||
message: 'Scroll to XBlock',
|
||||
payload: { scrollOffset }
|
||||
}, document.referrer
|
||||
);
|
||||
localStorage.removeItem('modalEditLastYPosition');
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
block_added: options && options.block_added
|
||||
});
|
||||
@@ -395,8 +444,16 @@ function($, _, Backbone, gettext, BasePage,
|
||||
const isAccessButton = event.currentTarget.className === 'access-button';
|
||||
const primaryHeader = $(event.target).closest('.xblock-header-primary, .nav-actions');
|
||||
const usageId = encodeURI(primaryHeader.attr('data-usage-id'));
|
||||
|
||||
try {
|
||||
if (this.options.isIframeEmbed && isAccessButton) {
|
||||
window.parent.postMessage(
|
||||
{
|
||||
type: 'toggleCourseXBlockDropdown',
|
||||
message: 'Adjust the height of the dropdown menu',
|
||||
payload: { courseXBlockDropdownHeight: 0 }
|
||||
}, document.referrer
|
||||
);
|
||||
return window.parent.postMessage(
|
||||
{
|
||||
type: 'manageXBlockAccess',
|
||||
@@ -422,11 +479,12 @@ function($, _, Backbone, gettext, BasePage,
|
||||
|| (useNewProblemEditor === 'True' && blockType === 'problem')
|
||||
) {
|
||||
var destinationUrl = primaryHeader.attr('authoring_MFE_base_url')
|
||||
+ '/' + blockType
|
||||
+ '/' + encodeURI(primaryHeader.attr('data-usage-id'));
|
||||
+ '/' + blockType
|
||||
+ '/' + encodeURI(primaryHeader.attr('data-usage-id'));
|
||||
|
||||
try {
|
||||
if (this.options.isIframeEmbed) {
|
||||
localStorage.setItem('modalEditLastYPosition', event.clientY.toString());
|
||||
return window.parent.postMessage(
|
||||
{
|
||||
type: 'newXBlockEditor',
|
||||
@@ -615,7 +673,7 @@ function($, _, Backbone, gettext, BasePage,
|
||||
type: 'toggleCourseXBlockDropdown',
|
||||
message: 'Adjust the height of the dropdown menu',
|
||||
payload: {
|
||||
courseXBlockDropdownHeight: courseXBlockDropdownHeight / 2,
|
||||
courseXBlockDropdownHeight: courseXBlockDropdownHeight / 2,
|
||||
},
|
||||
}, document.referrer
|
||||
);
|
||||
@@ -735,13 +793,26 @@ function($, _, Backbone, gettext, BasePage,
|
||||
const usageId = encodeURI(primaryHeader.attr('data-usage-id'));
|
||||
try {
|
||||
if (this.options.isIframeEmbed) {
|
||||
return window.parent.postMessage(
|
||||
window.parent.postMessage(
|
||||
{
|
||||
type: 'duplicateXBlock',
|
||||
message: 'Duplicate the XBlock',
|
||||
payload: { blockType, usageId }
|
||||
}, document.referrer
|
||||
);
|
||||
window.parent.postMessage(
|
||||
{
|
||||
type: 'toggleCourseXBlockDropdown',
|
||||
message: 'Adjust the height of the dropdown menu',
|
||||
payload: { courseXBlockDropdownHeight: 0 }
|
||||
}, document.referrer
|
||||
);
|
||||
// Saves the height of the XBlock during duplication with the new editor.
|
||||
// After closing the editor, the page scrolls to the newly created copy of the XBlock.
|
||||
if (['html', 'problem', 'video'].includes(blockType)) {
|
||||
const scrollHeight = event.clientY + this.findXBlockElement(event.target).height();
|
||||
localStorage.setItem('modalEditLastYPosition', scrollHeight.toString());
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
@@ -768,17 +839,24 @@ function($, _, Backbone, gettext, BasePage,
|
||||
type: 'showMoveXBlockModal',
|
||||
payload: {
|
||||
sourceXBlockInfo: {
|
||||
id: sourceXBlockInfo.attributes.id,
|
||||
displayName: sourceXBlockInfo.attributes.display_name,
|
||||
id: sourceXBlockInfo.attributes.id,
|
||||
displayName: sourceXBlockInfo.attributes.display_name,
|
||||
},
|
||||
sourceParentXBlockInfo: {
|
||||
id: sourceParentXBlockInfo.attributes.id,
|
||||
category: sourceParentXBlockInfo.attributes.category,
|
||||
hasChildren: sourceParentXBlockInfo.attributes.has_children,
|
||||
id: sourceParentXBlockInfo.attributes.id,
|
||||
category: sourceParentXBlockInfo.attributes.category,
|
||||
hasChildren: sourceParentXBlockInfo.attributes.has_children,
|
||||
},
|
||||
},
|
||||
}, document.referrer
|
||||
);
|
||||
window.parent.postMessage(
|
||||
{
|
||||
type: 'toggleCourseXBlockDropdown',
|
||||
message: 'Adjust the height of the dropdown menu',
|
||||
payload: { courseXBlockDropdownHeight: 0 }
|
||||
}, document.referrer
|
||||
);
|
||||
return true;
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -795,13 +873,20 @@ function($, _, Backbone, gettext, BasePage,
|
||||
const usageId = encodeURI(primaryHeader.attr('data-usage-id'));
|
||||
try {
|
||||
if (this.options.isIframeEmbed) {
|
||||
return window.parent.postMessage(
|
||||
window.parent.postMessage(
|
||||
{
|
||||
type: 'deleteXBlock',
|
||||
message: 'Delete the XBlock',
|
||||
payload: { usageId }
|
||||
}, document.referrer
|
||||
);
|
||||
window.parent.postMessage(
|
||||
{
|
||||
type: 'toggleCourseXBlockDropdown',
|
||||
message: 'Adjust the height of the dropdown menu',
|
||||
payload: { courseXBlockDropdownHeight: 0 }
|
||||
}, document.referrer
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
@@ -809,21 +894,42 @@ function($, _, Backbone, gettext, BasePage,
|
||||
this.deleteComponent(this.findXBlockElement(event.target));
|
||||
},
|
||||
|
||||
createComponent: function(template, target) {
|
||||
createPlaceholderElement: function() {
|
||||
return $('<div/>', {class: 'studio-xblock-wrapper'});
|
||||
},
|
||||
|
||||
createComponent: function(template, target, iframeMessageData) {
|
||||
// A placeholder element is created in the correct location for the new xblock
|
||||
// and then onNewXBlock will replace it with a rendering of the xblock. Note that
|
||||
// for xblocks that can't be replaced inline, the entire parent will be refreshed.
|
||||
var parentElement = this.findXBlockElement(target),
|
||||
parentLocator = parentElement.data('locator'),
|
||||
buttonPanel = target.closest('.add-xblock-component'),
|
||||
listPanel = buttonPanel.prev(),
|
||||
scrollOffset = ViewUtils.getScrollOffset(buttonPanel),
|
||||
buttonPanel = target?.closest('.add-xblock-component'),
|
||||
listPanel = buttonPanel?.prev(),
|
||||
$placeholderEl = $(this.createPlaceholderElement()),
|
||||
requestData = _.extend(template, {
|
||||
parent_locator: parentLocator
|
||||
}),
|
||||
placeholderElement;
|
||||
placeholderElement = $placeholderEl.appendTo(listPanel);
|
||||
scrollOffset,
|
||||
placeholderElement,
|
||||
$container;
|
||||
|
||||
if (this.options.isIframeEmbed) {
|
||||
$container = $('ol.reorderable-container.ui-sortable');
|
||||
scrollOffset = 0;
|
||||
} else {
|
||||
$container = listPanel;
|
||||
scrollOffset = ViewUtils.getScrollOffset(buttonPanel);
|
||||
}
|
||||
|
||||
placeholderElement = $placeholderEl.appendTo($container);
|
||||
|
||||
if (this.options.isIframeEmbed) {
|
||||
if (iframeMessageData.payload.data && iframeMessageData.type === 'addXBlock') {
|
||||
return this.onNewXBlock(placeholderElement, scrollOffset, false, iframeMessageData.payload.data);
|
||||
}
|
||||
}
|
||||
|
||||
return $.postJSON(this.getURLRoot() + '/', requestData,
|
||||
_.bind(this.onNewXBlock, this, placeholderElement, scrollOffset, false))
|
||||
.fail(function() {
|
||||
@@ -843,6 +949,32 @@ function($, _, Backbone, gettext, BasePage,
|
||||
placeholderElement;
|
||||
|
||||
placeholderElement = $placeholderEl.insertAfter(xblockElement);
|
||||
|
||||
if (this.options.isIframeEmbed) {
|
||||
try {
|
||||
window.parent.postMessage(
|
||||
{
|
||||
type: 'scrollToXBlock',
|
||||
message: 'Scroll to XBlock',
|
||||
payload: { scrollOffset: xblockElement.height() }
|
||||
}, document.referrer
|
||||
);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
const messageHandler = ({ data }) => {
|
||||
if (data && data.type === 'completeXBlockDuplicating') {
|
||||
self.onNewXBlock(placeholderElement, null, true, data.payload);
|
||||
window.removeEventListener('message', messageHandler);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('message', messageHandler);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
XBlockUtils.duplicateXBlock(xblockElement, parentElement)
|
||||
.done(function(data) {
|
||||
self.onNewXBlock(placeholderElement, scrollOffset, true, data);
|
||||
@@ -858,6 +990,21 @@ function($, _, Backbone, gettext, BasePage,
|
||||
xblockInfo = new XBlockInfo({
|
||||
id: xblockElement.data('locator')
|
||||
});
|
||||
|
||||
if (this.options.isIframeEmbed) {
|
||||
const messageHandler = ({ data }) => {
|
||||
if (data && data.type === 'completeXBlockDeleting') {
|
||||
const targetXBlockElement = $(`[data-locator="${data.payload.locator}"]`);
|
||||
window.removeEventListener('message', messageHandler);
|
||||
return self.onDelete(targetXBlockElement);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('message', messageHandler);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
XBlockUtils.deleteXBlock(xblockInfo).done(function() {
|
||||
self.onDelete(xblockElement);
|
||||
});
|
||||
@@ -986,7 +1133,9 @@ function($, _, Backbone, gettext, BasePage,
|
||||
window.location.href = destinationUrl;
|
||||
return;
|
||||
}
|
||||
ViewUtils.setScrollOffset(xblockElement, scrollOffset);
|
||||
if (!this.options.isIframeEmbed) {
|
||||
ViewUtils.setScrollOffset(xblockElement, scrollOffset);
|
||||
}
|
||||
xblockElement.data('locator', data.locator);
|
||||
return this.refreshXBlock(xblockElement, true, is_duplicate);
|
||||
},
|
||||
@@ -1003,7 +1152,9 @@ function($, _, Backbone, gettext, BasePage,
|
||||
parentElement = xblockElement.parent(),
|
||||
rootLocator = this.xblockView.model.id;
|
||||
if (xblockElement.length === 0 || xblockElement.data('locator') === rootLocator) {
|
||||
this.render({refresh: true, block_added: block_added});
|
||||
if (block_added) {
|
||||
this.render({refresh: true, block_added: block_added});
|
||||
}
|
||||
} else if (parentElement.hasClass('reorderable-container')) {
|
||||
this.refreshChildXBlock(xblockElement, block_added, is_duplicate);
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user