diff --git a/addons/website/static/src/js/editor/snippets.options.js b/addons/website/static/src/js/editor/snippets.options.js index eb6296ce526..a6ebe3f9fe0 100644 --- a/addons/website/static/src/js/editor/snippets.options.js +++ b/addons/website/static/src/js/editor/snippets.options.js @@ -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; diff --git a/addons/website/static/src/snippets/s_popup/options.js b/addons/website/static/src/snippets/s_popup/options.js index d7f6bfd9628..71814c4f64c 100644 --- a/addons/website/static/src/snippets/s_popup/options.js +++ b/addons/website/static/src/snippets/s_popup/options.js @@ -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'); }); }, /**