diff --git a/addons/website/static/src/js/editor/snippets.options.js b/addons/website/static/src/js/editor/snippets.options.js
index 6ae66ec6690..98d00337e0a 100644
--- a/addons/website/static/src/js/editor/snippets.options.js
+++ b/addons/website/static/src/js/editor/snippets.options.js
@@ -2671,27 +2671,18 @@ options.registry.HideFooter = VisibilityPageOptionUpdate.extend({
options.registry.anchor = options.Class.extend({
isTopOption: true,
- //--------------------------------------------------------------------------
- // Public
- //--------------------------------------------------------------------------
-
/**
* @override
*/
- start: function () {
+ start() {
// Generate anchor and copy it to clipboard on click, show the tooltip on success
- this.$button = this.$el.find('we-button');
- const clipboard = new ClipboardJS(this.$button[0], {text: () => this._getAnchorLink()});
- clipboard.on('success', () => {
- const message = sprintf(Markup(_t("Anchor copied to clipboard
Link: %s")), this._getAnchorLink());
- this.displayNotification({
- type: 'success',
- message: message,
- buttons: [{text: _t("Edit"), click: () => this.openAnchorDialog(), primary: true}],
- });
- });
+ const buttonEl = this.el.querySelector("we-button");
+ this.isModal = this.$target[0].classList.contains("modal");
+ if (buttonEl && !this.isModal) {
+ this._buildClipboard(buttonEl);
+ }
- return this._super.apply(this, arguments);
+ return this._super(...arguments);
},
/**
* @override
@@ -2701,13 +2692,45 @@ options.registry.anchor = options.Class.extend({
this.$target.filter(':not(.carousel)').removeAttr('id');
},
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ notify(name, data) {
+ this._super(...arguments);
+ if (name === "modalAnchor") {
+ this._buildClipboard(data.buttonEl);
+ }
+ },
+
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
+
/**
- * @see this.selectClass for parameters
+ * @private
+ * @param {Element} buttonEl
*/
- openAnchorDialog: function (previewMode, widgetValue, params) {
+ _buildClipboard(buttonEl) {
+ const clipboard = new ClipboardJS(buttonEl, {text: () => this._getAnchorLink()});
+ clipboard.on("success", () => {
+ const message = sprintf(Markup(_t("Anchor copied to clipboard
Link: %s")), this._getAnchorLink());
+ this.displayNotification({
+ type: "success",
+ message: message,
+ buttons: [{text: _t("Edit"), click: () => this._openAnchorDialog(buttonEl), primary: true}],
+ });
+ });
+ },
+
+ /**
+ * @private
+ * @param {Element} buttonEl
+ */
+ _openAnchorDialog(buttonEl) {
var self = this;
var buttons = [{
text: _t("Save & copy"),
@@ -2728,7 +2751,7 @@ options.registry.anchor = options.Class.extend({
if (!alreadyExists) {
self._setAnchorName(anchorName);
this.close();
- self.$button[0].click();
+ buttonEl.click();
}
},
}, {
@@ -2760,10 +2783,10 @@ options.registry.anchor = options.Class.extend({
*/
_setAnchorName: function (value) {
if (value) {
- this.$target.attr({
- 'id': value,
- 'data-anchor': true,
- });
+ this.$target[0].id = value;
+ if (!this.isModal) {
+ this.$target[0].dataset.anchor = true;
+ }
} else {
this.$target.removeAttr('id data-anchor');
}
@@ -2786,7 +2809,8 @@ options.registry.anchor = options.Class.extend({
}
this._setAnchorName(anchorName + n);
}
- return `${this.ownerDocument.location.pathname}#${this.$target[0].id}`;
+ const pathName = this.isModal ? "" : this.ownerDocument.location.pathname;
+ return `${pathName}#${this.$target[0].id}`;
},
/**
* Creates a safe id/anchor from text.
diff --git a/addons/website/static/src/js/tours/tour_utils.js b/addons/website/static/src/js/tours/tour_utils.js
index 2db178ce20b..f7b4b195bdb 100644
--- a/addons/website/static/src/js/tours/tour_utils.js
+++ b/addons/website/static/src/js/tours/tour_utils.js
@@ -108,6 +108,7 @@ function changeOption(optionName, weName = '', optionTooltipLabel = '', position
trigger: `${option_block} ${weName}, ${option_block} [title='${weName}']`,
content: Markup(sprintf(_t("Click on this option to change the %s of the block."), optionTooltipLabel)),
position: position,
+ in_modal: false,
run: "click",
};
}
diff --git a/addons/website/static/src/js/utils.js b/addons/website/static/src/js/utils.js
index 6b4dfd84f62..3712656df4c 100644
--- a/addons/website/static/src/js/utils.js
+++ b/addons/website/static/src/js/utils.js
@@ -22,7 +22,7 @@ function loadAnchors(url, body) {
resolve();
}
}).then(function (response) {
- const anchors = $(response).find('[id][data-anchor=true]').toArray().map((el) => {
+ const anchors = $(response).find('[id][data-anchor=true], .modal[id][data-display="onClick"]').toArray().map((el) => {
return '#' + el.id;
});
// Always suggest the top and the bottom of the page as internal link
diff --git a/addons/website/static/src/snippets/s_popup/000.js b/addons/website/static/src/snippets/s_popup/000.js
index e1f12192315..63b902ebadc 100644
--- a/addons/website/static/src/snippets/s_popup/000.js
+++ b/addons/website/static/src/snippets/s_popup/000.js
@@ -81,9 +81,21 @@ const PopupWidget = publicWidget.Widget.extend({
* @override
*/
start: function () {
- this._popupAlreadyShown = !!getCookie(this.$el.attr('id'));
- if (!this._popupAlreadyShown) {
- this._bindPopup();
+ this.modalShownOnClickEl = this.el.querySelector(".modal[data-display='onClick']");
+ if (this.modalShownOnClickEl) {
+ // We add a "hashchange" listener in case a button to open a popup
+ // is clicked.
+ this.__onHashChange = this._onHashChange.bind(this);
+ window.addEventListener('hashchange', this.__onHashChange);
+ // Check if a hash exists and if the modal needs to be opened when
+ // the page loads (e.g. The user has clicked a button on the
+ // "Contact us" page to open a popup on the homepage).
+ this._showPopupOnClick();
+ } else {
+ this._popupAlreadyShown = !!getCookie(this.$el.attr('id'));
+ if (!this._popupAlreadyShown) {
+ this._bindPopup();
+ }
}
return this._super(...arguments);
},
@@ -95,6 +107,9 @@ const PopupWidget = publicWidget.Widget.extend({
$(document).off('mouseleave.open_popup');
this.$el.find('.modal').modal('hide');
clearTimeout(this.timeout);
+ if (this.modalShownOnClickEl) {
+ window.removeEventListener('hashchange', this.__onHashChange);
+ }
},
//--------------------------------------------------------------------------
@@ -119,7 +134,7 @@ const PopupWidget = publicWidget.Widget.extend({
if (display === 'afterDelay') {
this.timeout = setTimeout(() => this._showPopup(), delay);
- } else {
+ } else if (display === "mouseExit") {
$(document).on('mouseleave.open_popup', () => this._showPopup());
}
},
@@ -144,6 +159,21 @@ const PopupWidget = publicWidget.Widget.extend({
}
this.$el.find('.modal').modal('show');
},
+ /**
+ * @private
+ */
+ _showPopupOnClick() {
+ const hash = window.location.hash;
+ // If a hash exists in the URL and it corresponds to the ID of the modal,
+ // then we open the modal.
+ if (hash && hash.substring(1) === this.modalShownOnClickEl.id) {
+ // We remove the hash from the URL because otherwise the popup
+ // cannot open again after being closed.
+ const urlWithoutHash = window.location.href.replace(hash, '');
+ window.history.replaceState(null, null, urlWithoutHash);
+ this._showPopup();
+ }
+ },
//--------------------------------------------------------------------------
// Handlers
@@ -161,7 +191,7 @@ const PopupWidget = publicWidget.Widget.extend({
_onHideModal: function () {
const nbDays = this.$el.find('.modal').data('consentsDuration');
setCookie(this.el.id, this.cookieValue, nbDays * 24 * 60 * 60, 'required');
- this._popupAlreadyShown = true;
+ this._popupAlreadyShown = true && !this.modalShownOnClickEl;
this.$el.find('.media_iframe_video iframe').each((i, iframe) => {
iframe.src = '';
@@ -176,6 +206,12 @@ const PopupWidget = publicWidget.Widget.extend({
iframe.src = media.dataset.oeExpression || media.dataset.src; // TODO still oeExpression to remove someday
});
},
+ /**
+ * @private
+ */
+ _onHashChange() {
+ this._showPopupOnClick();
+ },
});
publicWidget.registry.popup = PopupWidget;
diff --git a/addons/website/static/src/snippets/s_popup/options.js b/addons/website/static/src/snippets/s_popup/options.js
index c611f8841cc..a1ab208afa1 100644
--- a/addons/website/static/src/snippets/s_popup/options.js
+++ b/addons/website/static/src/snippets/s_popup/options.js
@@ -31,6 +31,15 @@ options.registry.SnippetPopup = options.Class.extend({
// sound). Stop the video, as the user can't do it (no button on video
// in edit mode).
this._removeIframeSrc();
+ if (!this.$target[0].parentElement.matches("#website_cookies_bar")) {
+ this.trigger_up("option_update", {
+ optionName: "anchor",
+ name: "modalAnchor",
+ data: {
+ buttonEl: this._requestUserValueWidgets("onclick_opt")[0].el,
+ },
+ });
+ }
return this._super(...arguments);
},
/**
diff --git a/addons/website/static/src/snippets/s_website_form/000.js b/addons/website/static/src/snippets/s_website_form/000.js
index 7703952e016..ed5fa635aad 100644
--- a/addons/website/static/src/snippets/s_website_form/000.js
+++ b/addons/website/static/src/snippets/s_website_form/000.js
@@ -401,10 +401,18 @@
if (successPage.charAt(0) === "#") {
const successAnchorEl = document.getElementById(successPage.substring(1));
if (successAnchorEl) {
- await dom.scrollTo(successAnchorEl, {
- duration: 500,
- extraOffset: 0,
- });
+ // Check if the target of the link is a modal.
+ if (successAnchorEl.classList.contains("modal")) {
+ // Trigger a "hashChange" event to
+ // notify the popup widget to show the
+ // popup.
+ window.location.href = successPage;
+ } else {
+ await dom.scrollTo(successAnchorEl, {
+ duration: 500,
+ extraOffset: 0,
+ });
+ }
}
break;
}
diff --git a/addons/website/static/tests/tours/snippet_popup_display_on_click.js b/addons/website/static/tests/tours/snippet_popup_display_on_click.js
new file mode 100644
index 00000000000..81daa6c7a76
--- /dev/null
+++ b/addons/website/static/tests/tours/snippet_popup_display_on_click.js
@@ -0,0 +1,85 @@
+/** @odoo-module */
+
+import wTourUtils from "website.tour_utils";
+
+wTourUtils.registerWebsitePreviewTour("snippet_popup_display_on_click", {
+ test: true,
+ url: "/",
+ edition: true,
+}, [
+ wTourUtils.dragNDrop({id: "s_text_image", name: "Image - Text"}),
+ wTourUtils.dragNDrop({id: "s_popup", name: "Popup"}),
+ {
+ content: "Click inside the popup to access its options menu.",
+ in_modal: false,
+ trigger: "iframe .s_popup .s_banner",
+ },
+ wTourUtils.changeOption("SnippetPopup", 'we-select[data-attribute-name="display"] we-toggler'),
+ {
+ content: "Click on the display 'On Click' option",
+ trigger: "#oe_snippets we-button[data-name='onclick_opt']",
+ in_modal: false,
+ run() {
+ // The clipboard cannot be accessed from a script.
+ // https://w3c.github.io/editing/docs/execCommand/#dfn-the-copy-command
+ // The execCommand is patched for that step so that ClipboardJS still
+ // sends the "success" event.
+ const oldExecCommand = document.execCommand;
+ document.execCommand = () => true;
+ this.$anchor[0].click();
+ document.execCommand = oldExecCommand;
+ }
+ },
+ {
+ content: "Check the copied anchor from the notification toast",
+ trigger: ".o_notification_manager .o_notification_content",
+ run() {
+ const notificationContent = this.$anchor[0].innerText;
+ const anchor = notificationContent.substring(notificationContent.indexOf("#"));
+
+ if (anchor !== "#Win-%2420") {
+ console.error("The popup anchor is not '#Win-%2420' as expected.");
+ }
+ },
+ },
+ wTourUtils.clickOnElement("button to close the popup", "iframe .s_popup_close"),
+ wTourUtils.clickOnElement("text image snippet button", "iframe .s_text_image .btn-primary"),
+ {
+ content: "Paste the popup anchor in the URL input",
+ trigger: "#o_link_dialog_url_input",
+ run: "text #Win-%2420"
+ },
+ ...wTourUtils.clickOnSave(),
+ wTourUtils.clickOnElement("text image snippet button", "iframe .s_text_image .btn-primary"),
+ {
+ content: "Verify that the popup opens after clicked the button.",
+ in_modal: false,
+ trigger: "iframe .s_popup .modal[id='Win-%2420'].show",
+ },
+ wTourUtils.clickOnElement("button to close the popup", "iframe .s_popup_close"),
+ {
+ content: "Go to the 'contactus' page.",
+ trigger: "iframe a[href='/contactus']",
+ },
+ {
+ content: "wait for the page to be loaded",
+ trigger: ".o_website_preview[data-view-xmlid='website.contactus']",
+ run: () => null, // it"s a check
+ },
+ ...wTourUtils.clickOnEditAndWaitEditMode(),
+ wTourUtils.dragNDrop({id: "s_text_image", name: "Image - Text"}),
+ wTourUtils.clickOnElement("text image snippet button", "iframe .s_text_image .btn-primary"),
+ {
+ content: "Add a link to the homepage popup in the URL input",
+ trigger: "#o_link_dialog_url_input",
+ run: "text /#Win-%2420"
+ },
+ ...wTourUtils.clickOnSave(),
+ wTourUtils.clickOnElement("text image snippet button", "iframe .s_text_image .btn-primary"),
+ {
+ content: "Verify that the popup opens when the homepage page loads.",
+ in_modal: false,
+ extra_trigger: ".o_website_preview[data-view-xmlid='website.homepage']",
+ trigger: "iframe .s_popup .modal[id='Win-%2420'].show",
+ },
+]);
diff --git a/addons/website/tests/test_snippets.py b/addons/website/tests/test_snippets.py
index 7a1504f95f4..93e3cd47b92 100644
--- a/addons/website/tests/test_snippets.py
+++ b/addons/website/tests/test_snippets.py
@@ -74,3 +74,6 @@ class TestSnippets(HttpCase):
def test_10_parallax(self):
self.start_tour(self.env['website'].get_client_action_url('/'), 'test_parallax', login='admin')
+
+ def test_11_snippet_popup_display_on_click(self):
+ self.start_tour(self.env['website'].get_client_action_url('/'), 'snippet_popup_display_on_click', login='admin')
diff --git a/addons/website/views/snippets/s_popup.xml b/addons/website/views/snippets/s_popup.xml
index 73215922fdf..f710d37f917 100644
--- a/addons/website/views/snippets/s_popup.xml
+++ b/addons/website/views/snippets/s_popup.xml
@@ -53,10 +53,11 @@
Delay
On Exit
+ On Click (via link)
days
-
+
This page
All pages
diff --git a/addons/website/views/snippets/snippets.xml b/addons/website/views/snippets/snippets.xml
index d660b4cb705..78af05a43af 100644
--- a/addons/website/views/snippets/snippets.xml
+++ b/addons/website/views/snippets/snippets.xml
@@ -1120,6 +1120,11 @@
data-no-preview="true"/>
+
+
+