[FIX] website: adapt bootstrap elements' options to iframe edition
Commit [1] moved the edition of website from the frontend to the backend and in doing so, introduced an iframe from which the website is edited. This broke some behaviours as the event binding for jQuery widgets (such as the carousel) is no longer done from the same jQuery as the editor. So bootstrap events triggered inside the iframe do not reach options that are listening using the top window's jQuery (simple $()). This commit adds the $bsTarget property to the SnippetOption class. It wraps $target[0] using the iframe's jQuery instance. This allows for options to listen to bootstrap events. (using $bsTarget.on) Options that were doing that previously are adapted by this commit. (Carousel, Popup, Accordion (collapse)) Note: Popup was already adapted locally (by [2]), this commit only changes its use of an $iframeTarget property to the $bsTarget one. This commit also fixes "Add item" on accordion snippet to properly assign a unique ID to the new tab. [1]: https://github.com/odoo/odoo/commit/31cc10b91dc7762e23b4bde9b945be0c4ce3fe3b [2]: https://github.com/odoo/odoo/commit/03c552690b15cbf2e7d6b7812386ac64042219af task-2687506 Part-of: odoo/odoo#94564
This commit is contained in:
committed by
qsm-odoo
parent
ddf2e74b4c
commit
f7055d3dba
@@ -406,6 +406,21 @@ options.Class.include({
|
||||
}),
|
||||
specialCheckAndReloadMethodsNames: ['customizeWebsiteViews', 'customizeWebsiteVariable', 'customizeWebsiteColor'],
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
// Since the website is displayed in an iframe, its jQuery
|
||||
// instance is not the same as the editor. This property allows
|
||||
// for easy access to bootstrap plugins (Carousel, Modal, ...).
|
||||
// This is only needed because jQuery doesn't send custom events
|
||||
// the same way native javascript does. So if a jQuery instance
|
||||
// triggers a custom event, only that same jQuery instance will
|
||||
// trigger handlers set with `.on`.
|
||||
this.$bsTarget = this.ownerDocument.defaultView.$(this.$target[0]);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Options
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -1494,7 +1509,7 @@ options.registry.Carousel = options.Class.extend({
|
||||
* @override
|
||||
*/
|
||||
start: function () {
|
||||
this.$target.carousel('pause');
|
||||
this.$bsTarget.carousel('pause');
|
||||
this.$indicators = this.$target.find('.carousel-indicators');
|
||||
this.$controls = this.$target.find('.carousel-control-prev, .carousel-control-next, .carousel-indicators');
|
||||
|
||||
@@ -1504,11 +1519,11 @@ options.registry.Carousel = options.Class.extend({
|
||||
this.$controls.addClass('o_we_no_overlay');
|
||||
|
||||
let _slideTimestamp;
|
||||
this.$target.on('slide.bs.carousel.carousel_option', () => {
|
||||
this.$bsTarget.on('slide.bs.carousel.carousel_option', () => {
|
||||
_slideTimestamp = window.performance.now();
|
||||
setTimeout(() => this.trigger_up('hide_overlay'));
|
||||
});
|
||||
this.$target.on('slid.bs.carousel.carousel_option', () => {
|
||||
this.$bsTarget.on('slid.bs.carousel.carousel_option', () => {
|
||||
// slid.bs.carousel is most of the time fired too soon by bootstrap
|
||||
// since it emulates the transitionEnd with a setTimeout. We wait
|
||||
// here an extra 20% of the time before retargeting edition, which
|
||||
@@ -1519,7 +1534,7 @@ options.registry.Carousel = options.Class.extend({
|
||||
$snippet: this.$target.find('.carousel-item.active'),
|
||||
ifInactiveOptions: true,
|
||||
});
|
||||
this.$target.trigger('active_slide_targeted');
|
||||
this.$bsTarget.trigger('active_slide_targeted');
|
||||
}, 0.2 * _slideDuration);
|
||||
});
|
||||
|
||||
@@ -1530,7 +1545,7 @@ options.registry.Carousel = options.Class.extend({
|
||||
*/
|
||||
destroy: function () {
|
||||
this._super.apply(this, arguments);
|
||||
this.$target.off('.carousel_option');
|
||||
this.$bsTarget.off('.carousel_option');
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
@@ -1614,7 +1629,7 @@ options.registry.Carousel = options.Class.extend({
|
||||
$active.clone(false)
|
||||
.removeClass('active')
|
||||
.insertAfter($active);
|
||||
this.$target.carousel('next');
|
||||
this.$bsTarget.carousel('next');
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1626,7 +1641,7 @@ options.registry.CarouselItem = options.Class.extend({
|
||||
* @override
|
||||
*/
|
||||
start: function () {
|
||||
this.$carousel = this.$target.closest('.carousel');
|
||||
this.$carousel = this.$bsTarget.closest('.carousel');
|
||||
this.$indicators = this.$carousel.find('.carousel-indicators');
|
||||
this.$controls = this.$carousel.find('.carousel-control-prev, .carousel-control-next, .carousel-indicators');
|
||||
|
||||
@@ -1684,10 +1699,9 @@ options.registry.CarouselItem = options.Class.extend({
|
||||
const $items = this.$carousel.find('.carousel-item');
|
||||
const newLength = $items.length - 1;
|
||||
if (!this.removing && newLength > 0) {
|
||||
const $toDelete = $items.filter('.active');
|
||||
const $toDelete = $items.filter('.active').add(this.$indicators.find('.active'));
|
||||
this.$carousel.one('active_slide_targeted.carousel_item_option', () => {
|
||||
$toDelete.remove();
|
||||
this.$indicators.find('li:last').remove();
|
||||
this.$controls.toggleClass('d-none', newLength === 1);
|
||||
this.$carousel.trigger('content_changed');
|
||||
this.removing = false;
|
||||
@@ -1863,7 +1877,7 @@ options.registry.collapse = options.Class.extend({
|
||||
*/
|
||||
start: function () {
|
||||
var self = this;
|
||||
this.$target.on('shown.bs.collapse hidden.bs.collapse', '[role="tabpanel"]', function () {
|
||||
this.$bsTarget.on('shown.bs.collapse hidden.bs.collapse', '[role="tabpanel"]', function () {
|
||||
self.trigger_up('cover_update');
|
||||
self.$target.trigger('content_changed');
|
||||
});
|
||||
@@ -1886,7 +1900,7 @@ options.registry.collapse = options.Class.extend({
|
||||
*/
|
||||
onMove: function () {
|
||||
this._createIDs();
|
||||
var $panel = this.$target.find('.collapse').removeData('bs.collapse');
|
||||
var $panel = this.$bsTarget.find('.collapse').removeData('bs.collapse');
|
||||
if ($panel.attr('aria-expanded') === 'true') {
|
||||
$panel.closest('.accordion').find('.collapse[aria-expanded="true"]')
|
||||
.filter((i, el) => (el !== $panel[0]))
|
||||
@@ -1911,14 +1925,15 @@ options.registry.collapse = options.Class.extend({
|
||||
const $tablist = this.$target.closest('[role="tablist"]');
|
||||
const $tab = this.$target.find('[role="tab"]');
|
||||
const $panel = this.$target.find('[role="tabpanel"]');
|
||||
const $body = this.$target.closest('body');
|
||||
|
||||
const setUniqueId = ($elem, label) => {
|
||||
let elemId = $elem.attr('id');
|
||||
if (!elemId || $('[id="' + elemId + '"]').length > 1) {
|
||||
if (!elemId || $body.find('[id="' + elemId + '"]').length > 1) {
|
||||
do {
|
||||
time++;
|
||||
elemId = label + time;
|
||||
} while ($('#' + elemId).length);
|
||||
} while ($body.find('#' + elemId).length);
|
||||
$elem.attr('id', elemId);
|
||||
}
|
||||
return elemId;
|
||||
|
||||
@@ -8,19 +8,14 @@ options.registry.SnippetPopup = options.Class.extend({
|
||||
* @override
|
||||
*/
|
||||
start: function () {
|
||||
// Editor jQuery and iFrame jQuery isn't the same instance. This means
|
||||
// that events within the iframe (so popup events) aren't handled by
|
||||
// the editor's jQuery. To apply and listen to events on the modal,
|
||||
// we need to get it from the iframe's jQuery instance.
|
||||
this.$iframeTarget = this.$target[0].ownerDocument.defaultView.$(this.$target[0]);
|
||||
// Note: the link are excluded here so that internal modal buttons do
|
||||
// not close the popup as we want to allow edition of those buttons.
|
||||
this.$iframeTarget.on('click.SnippetPopup', '.js_close_popup:not(a, .btn)', ev => {
|
||||
this.$bsTarget.on('click.SnippetPopup', '.js_close_popup:not(a, .btn)', ev => {
|
||||
ev.stopPropagation();
|
||||
this.onTargetHide();
|
||||
this.trigger_up('snippet_option_visibility_update', {show: false});
|
||||
});
|
||||
this.$iframeTarget.on('shown.bs.modal.SnippetPopup', () => {
|
||||
this.$bsTarget.on('shown.bs.modal.SnippetPopup', () => {
|
||||
this.trigger_up('snippet_option_visibility_update', {show: true});
|
||||
// TODO duplicated code from the popup public widget, this should
|
||||
// be moved to a *video* public widget and be reviewed in master
|
||||
@@ -29,7 +24,7 @@ options.registry.SnippetPopup = options.Class.extend({
|
||||
iframe.src = media.dataset.oeExpression || media.dataset.src; // TODO still oeExpression to remove someday
|
||||
});
|
||||
});
|
||||
this.$iframeTarget.on('hide.bs.modal.SnippetPopup', () => {
|
||||
this.$bsTarget.on('hide.bs.modal.SnippetPopup', () => {
|
||||
this.trigger_up('snippet_option_visibility_update', {show: false});
|
||||
this._removeIframeSrc();
|
||||
});
|
||||
@@ -47,7 +42,7 @@ options.registry.SnippetPopup = options.Class.extend({
|
||||
// The video should not start before the modal opens, remove it from the
|
||||
// DOM. It will be added back on modal open to start the video.
|
||||
this._removeIframeSrc();
|
||||
this.$iframeTarget.off('.SnippetPopup');
|
||||
this.$bsTarget.off('.SnippetPopup');
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
@@ -65,7 +60,7 @@ options.registry.SnippetPopup = options.Class.extend({
|
||||
* @override
|
||||
*/
|
||||
onTargetShow: async function () {
|
||||
this.$iframeTarget.modal('show');
|
||||
this.$bsTarget.modal('show');
|
||||
$(this.$target[0].ownerDocument.body).children('.modal-backdrop:last').addClass('d-none');
|
||||
},
|
||||
/**
|
||||
@@ -74,14 +69,14 @@ options.registry.SnippetPopup = options.Class.extend({
|
||||
onTargetHide: async function () {
|
||||
return new Promise(resolve => {
|
||||
const timeoutID = setTimeout(() => {
|
||||
this.$iframeTarget.off('hidden.bs.modal.popup_on_target_hide');
|
||||
this.$bsTarget.off('hidden.bs.modal.popup_on_target_hide');
|
||||
resolve();
|
||||
}, 500);
|
||||
this.$iframeTarget.one('hidden.bs.modal.popup_on_target_hide', () => {
|
||||
this.$bsTarget.one('hidden.bs.modal.popup_on_target_hide', () => {
|
||||
clearTimeout(timeoutID);
|
||||
resolve();
|
||||
});
|
||||
this.$iframeTarget.modal('hide');
|
||||
this.$bsTarget.modal('hide');
|
||||
});
|
||||
},
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user