odoo18/addons/website_sale/static/src/js/website_sale.js

884 lines
33 KiB
JavaScript

/** @odoo-module **/
import publicWidget from "@web/legacy/js/public/public_widget";
import VariantMixin from "@website_sale/js/sale_variant_mixin";
import wSaleUtils from "@website_sale/js/website_sale_utils";
const cartHandlerMixin = wSaleUtils.cartHandlerMixin;
import "@website/libs/zoomodoo/zoomodoo";
import {extraMenuUpdateCallbacks} from "@website/js/content/menu";
import { ProductImageViewer } from "@website_sale/js/components/website_sale_image_viewer";
import { rpc } from "@web/core/network/rpc";
import { debounce, throttleForAnimation } from "@web/core/utils/timing";
import { listenSizeChange, SIZES, utils as uiUtils } from "@web/core/ui/ui_service";
import { isBrowserFirefox, hasTouch } from "@web/core/browser/feature_detection";
import { Component } from "@odoo/owl";
export const WebsiteSale = publicWidget.Widget.extend(VariantMixin, cartHandlerMixin, {
selector: '.oe_website_sale',
events: Object.assign({}, VariantMixin.events || {}, {
'change form .js_product:first input[name="add_qty"]': '_onChangeAddQuantity',
'mouseup .js_publish': '_onMouseupPublish',
'touchend .js_publish': '_onMouseupPublish',
'change .oe_cart input.js_quantity[data-product-id]': '_onChangeCartQuantity',
'click .oe_cart a.js_add_suggested_products': '_onClickSuggestedProduct',
'click a.js_add_cart_json': '_onClickAddCartJSON',
'click .a-submit': '_onClickSubmit',
'change form.js_attributes input, form.js_attributes select': '_onChangeAttribute',
'mouseup form.js_add_cart_json label': '_onMouseupAddCartLabel',
'touchend form.js_add_cart_json label': '_onMouseupAddCartLabel',
'submit .o_wsale_products_searchbar_form': '_onSubmitSaleSearch',
'click .toggle_summary': '_onToggleSummary',
'click #add_to_cart, .o_we_buy_now, #products_grid .o_wsale_product_btn .a-submit': 'async _onClickAdd',
'click input.js_product_change': 'onChangeVariant',
'change .js_main_product [data-attribute_exclusions]': 'onChangeVariant',
'click .o_product_page_reviews_link': '_onClickReviewsLink',
'mousedown .o_wsale_filmstip_wrapper': '_onMouseDown',
'mouseleave .o_wsale_filmstip_wrapper': '_onMouseLeave',
'mouseup .o_wsale_filmstip_wrapper': '_onMouseUp',
'mousemove .o_wsale_filmstip_wrapper': '_onMouseMove',
'click .o_wsale_filmstip_wrapper' : '_onClickHandler',
'submit': '_onClickConfirmOrder',
}),
/**
* @constructor
*/
init: function () {
this._super.apply(this, arguments);
this._changeCartQuantity = debounce(this._changeCartQuantity.bind(this), 500);
this.isWebsite = true;
this.filmStripStartX = 0;
this.filmStripIsDown = false;
this.filmStripScrollLeft = 0;
this.filmStripMoved = false;
delete this.events['change [data-attribute_exclusions]'];
},
/**
* @override
*/
start() {
const def = this._super(...arguments);
this._applyHash();
// This has to be triggered to compute the "out of stock" feature and the hash variant changes
this.triggerVariantChange(this.$el);
listenSizeChange(() => {
if (uiUtils.getSize() === SIZES.XL) {
$('.toggle_summary_div').addClass('d-none d-xl-block');
}
})
this._startZoom();
// Triggered when selecting a variant of a product in a carousel element
window.addEventListener("hashchange", (ev) => {
this._applyHash();
this.triggerVariantChange(this.$el);
});
// This allows conditional styling for the filmstrip
const filmstripContainer = this.el.querySelector('.o_wsale_filmstip_container');
const filmstripContainerWidth = filmstripContainer
? filmstripContainer.getBoundingClientRect().width : 0;
const filmstripWrapper = this.el.querySelector('.o_wsale_filmstip_wrapper');
const filmstripWrapperWidth = filmstripWrapper
? filmstripWrapper.getBoundingClientRect().width : 0;
const isFilmstripScrollable = filmstripWrapperWidth < filmstripContainerWidth
if (isBrowserFirefox() || hasTouch() || isFilmstripScrollable) {
filmstripContainer?.classList.add('o_wsale_filmstip_fancy_disabled');
}
this.getRedirectOption();
return def;
},
destroy() {
this._super.apply(this, arguments);
this._cleanupZoom();
},
/**
* The selector is different when using list view of variants.
*
* @override
*/
getSelectedVariantValues: function ($container) {
var combination = $container.find('input.js_product_change:checked')
.data('combination');
if (combination) {
return combination;
}
return VariantMixin.getSelectedVariantValues.apply(this, arguments);
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
_onMouseDown: function (ev) {
this.filmStripIsDown = true;
this.filmStripStartX = ev.pageX - ev.currentTarget.offsetLeft;
this.filmStripScrollLeft = ev.currentTarget.scrollLeft;
this.formerTarget = ev.target;
this.filmStripMoved = false;
},
_onMouseLeave: function (ev) {
if (!this.filmStripIsDown) {
return;
}
ev.currentTarget.classList.remove('activeDrag');
this.filmStripIsDown = false
},
_onMouseUp: function (ev) {
this.filmStripIsDown = false;
ev.currentTarget.classList.remove('activeDrag');
},
_onMouseMove: function (ev) {
if (!this.filmStripIsDown) {
return;
}
ev.preventDefault();
ev.currentTarget.classList.add('activeDrag');
this.filmStripMoved = true;
const x = ev.pageX - ev.currentTarget.offsetLeft;
const walk = (x - this.filmStripStartX) * 2;
ev.currentTarget.scrollLeft = this.filmStripScrollLeft - walk;
},
_onClickHandler: function(ev) {
if(this.filmStripMoved) {
ev.stopPropagation();
ev.preventDefault();
}
},
_applyHash: function () {
const params = new URLSearchParams(window.location.hash.substring(1));
if (params.get("attribute_values")) {
const attributeValueIds = params.get("attribute_values").split(',');
const inputs = document.querySelectorAll(
'input.js_variant_change, select.js_variant_change option'
);
inputs.forEach((element) => {
if (attributeValueIds.includes(element.dataset.attributeValueId)) {
if (element.tagName === "INPUT") {
element.checked = true;
} else if (element.tagName === "OPTION") {
element.selected = true;
}
}
});
this._changeAttribute(['.css_attribute_color', '.o_variant_pills']);
}
},
/**
* Sets the url hash from the selected product options.
*
* @private
*/
_setUrlHash: function ($parent) {
const inputs = document.querySelectorAll(
'input.js_variant_change:checked, select.js_variant_change option:checked'
);
let attributeIds = [];
inputs.forEach((element) => attributeIds.push(element.dataset.attributeValueId));
if (attributeIds.length > 0) {
window.location.hash = `attribute_values=${attributeIds.join(',')}`;
}
},
/**
* Set the checked values active.
*
* @private
* @param {Array} valueSelectors Selectors
*/
_changeAttribute: function (valueSelectors) {
valueSelectors.forEach((selector) => {
$(selector).removeClass("active").filter(":has(input:checked)").addClass("active");
});
},
/**
* @private
*/
_changeCartQuantity: function ($input, value, $dom_optional, line_id, productIDs) {
$($dom_optional).toArray().forEach((elem) => {
$(elem).find('.js_quantity').text(value);
productIDs.push($(elem).find('span[data-product-id]').data('product-id'));
});
$input.data('update_change', true);
rpc("/shop/cart/update_json", {
line_id: line_id,
product_id: parseInt($input.data('product-id'), 10),
set_qty: value,
display: true,
}).then((data) => {
$input.data('update_change', false);
var check_value = parseInt($input.val() || 0, 10);
if (isNaN(check_value)) {
check_value = 1;
}
if (value !== check_value) {
$input.trigger('change');
return;
}
if (!data.cart_quantity) {
return window.location = '/shop/cart';
}
$input.val(data.quantity);
$('.js_quantity[data-line-id='+line_id+']').val(data.quantity).text(data.quantity);
wSaleUtils.updateCartNavBar(data);
wSaleUtils.showWarning(data.notification_info.warning);
// Propagating the change to the express checkout forms
Component.env.bus.trigger('cart_amount_changed', [data.amount, data.minor_amount]);
});
},
/**
* This is overridden to handle the "List View of Variants" of the web shop.
* That feature allows directly selecting the variant from a list instead of selecting the
* attribute values.
*
* Since the layout is completely different, we need to fetch the product_id directly
* from the selected variant.
*
* @override
*/
_getProductId: function ($parent) {
if ($parent.find('input.js_product_change').length !== 0) {
return parseInt($parent.find('input.js_product_change:checked').val());
}
else {
return VariantMixin._getProductId.apply(this, arguments);
}
},
_getProductImageLayout: function () {
return document.querySelector("#product_detail_main").dataset.image_layout;
},
_getProductImageWidth: function () {
return document.querySelector("#product_detail_main").dataset.image_width;
},
_getProductImageContainerSelector: function () {
return {
'carousel': "#o-carousel-product",
'grid': "#o-grid-product",
}[this._getProductImageLayout()];
},
_getProductImageContainer: function () {
return document.querySelector(this._getProductImageContainerSelector());
},
_isEditorEnabled() {
return document.body.classList.contains("editor_enable");
},
/**
* @private
*/
_startZoom: function () {
const salePage = document.querySelector(".o_wsale_product_page");
if (!salePage || this._getProductImageWidth() === "none") {
return;
}
this._cleanupZoom();
this.zoomCleanup = [];
// Zoom on hover (except on mobile)
if (salePage.dataset.ecomZoomAuto && !uiUtils.isSmall()) {
const images = salePage.querySelectorAll("img[data-zoom]");
for (const image of images) {
const $image = $(image);
const callback = () => {
$image.zoomOdoo({
event: "mouseenter",
attach: this._getProductImageContainerSelector(),
preventClicks: salePage.dataset.ecomZoomClick,
attachToTarget: this._getProductImageLayout() === "grid",
});
image.dataset.zoom = 1;
};
image.addEventListener('load', callback);
this.zoomCleanup.push(() => {
image.removeEventListener('load', callback);
const zoomOdoo = $image.data("zoomOdoo");
if (zoomOdoo) {
zoomOdoo.hide();
$image.unbind();
}
});
if (image.complete) {
callback();
}
}
}
// Zoom on click
if (salePage.dataset.ecomZoomClick) {
// In this case we want all the images not just the ones that are "zoomables"
const images = salePage.querySelectorAll(".product_detail_img");
for (const image of images ) {
const handler = () => {
if (salePage.dataset.ecomZoomAuto) {
// Remove any flyout
const flyouts = document.querySelectorAll(".zoomodoo-flyout");
for (const flyout of flyouts) {
flyout.remove();
}
}
this.call("dialog", "add", ProductImageViewer, {
selectedImageIdx: [...images].indexOf(image),
images,
});
};
image.addEventListener("click", handler);
this.zoomCleanup.push(() => {
image.removeEventListener("click", handler);
});
}
}
},
_cleanupZoom() {
if (!this.zoomCleanup || !this.zoomCleanup.length) {
return;
}
for (const cleanup of this.zoomCleanup) {
cleanup();
}
this.zoomCleanup = undefined;
},
/**
* On website, we display a carousel instead of only one image
*
* @override
* @private
*/
_updateProductImage: function ($productContainer, displayImage, productId, productTemplateId, newImages, isCombinationPossible) {
let $images = $productContainer.find(this._getProductImageContainerSelector());
// When using the web editor, don't reload this or the images won't
// be able to be edited depending on if this is done loading before
// or after the editor is ready.
if ($images.length && !this._isEditorEnabled()) {
const $newImages = $(newImages);
$images.after($newImages);
$images.remove();
$images = $newImages;
// Update the sharable image (only work for Pinterest).
const shareImageSrc = $images[0].querySelector('img').src;
document.querySelector('meta[property="og:image"]')
.setAttribute('content', shareImageSrc);
if ($images.attr('id') === 'o-carousel-product') {
$images.carousel(0);
}
this._startZoom();
// fix issue with carousel height
this.trigger_up('widgets_start_request', {$target: $images});
}
$images.toggleClass('css_not_available', !isCombinationPossible);
},
/**
* @private
* @param {MouseEvent} ev
*/
_onClickAdd: function (ev) {
ev.preventDefault();
var def = () => {
this.getCartHandlerOptions(ev);
return this._handleAdd($(ev.currentTarget).closest('form'));
};
if ($('.js_add_cart_variants').children().length) {
return this._getCombinationInfo(ev).then(() => {
return !$(ev.target).closest('.js_product').hasClass("css_not_available") ? def() : Promise.resolve();
});
}
return def();
},
/**
* Initializes the optional products modal
* and add handlers to the modal events (confirm, back, ...)
*
* @private
* @param {$.Element} $form the related webshop form
*/
_handleAdd: function ($form) {
var self = this;
this.$form = $form;
var productSelector = [
'input[type="hidden"][name="product_id"]',
'input[type="radio"][name="product_id"]:checked'
];
const productTemplateId =
parseInt($form.find('input[type="hidden"][name="product_template_id"]').first().val());
var productReady = this.selectOrCreateProduct(
$form,
parseInt($form.find(productSelector.join(', ')).first().val(), 10),
productTemplateId,
);
return productReady.then(function (productId) {
$form.find(productSelector.join(', ')).val(productId);
self._updateRootProduct($form, productId, productTemplateId);
return self._onProductReady($form.closest('.o_wsale_product_page').length > 0);
});
},
_onProductReady(isOnProductPage = false) {
return this._submitForm();
},
/**
* Add custom variant values and attribute values that do not generate variants
* in the params to submit form if 'stay on page' option is disabled, or call
* '_addToCartInPage' otherwise.
*
* @private
* @returns {Promise}
*/
_submitForm: function () {
const params = this.rootProduct;
const $product = $('#product_detail');
const productTrackingInfo = $product.data('product-tracking-info');
if (productTrackingInfo) {
productTrackingInfo.quantity = params.quantity;
$product.trigger('add_to_cart_event', [productTrackingInfo]);
}
params.add_qty = params.quantity;
params.product_custom_attribute_values = JSON.stringify(params.product_custom_attribute_values);
params.no_variant_attribute_values = JSON.stringify(params.no_variant_attribute_values);
delete params.quantity;
return this.addToCart(params);
},
/**
* @private
* @param {MouseEvent} ev
*/
_onClickAddCartJSON: function (ev) {
this.onClickAddCartJSON(ev);
},
/**
* @private
* @param {Event} ev
*/
_onChangeAddQuantity: function (ev) {
this.onChangeAddQuantity(ev);
},
/**
* @private
* @param {Event} ev
*/
_onMouseupPublish: function (ev) {
$(ev.currentTarget).parents('.thumbnail').toggleClass('disabled');
},
/**
* @private
* @param {Event} ev
*/
_onChangeCartQuantity: function (ev) {
var $input = $(ev.currentTarget);
if ($input.data('update_change')) {
return;
}
var value = parseInt($input.val() || 0, 10);
if (isNaN(value)) {
value = 1;
}
var $dom = $input.closest('tr');
// var default_price = parseFloat($dom.find('.text-danger > span.oe_currency_value').text());
var $dom_optional = $dom.nextUntil(':not(.optional_product.info)');
var line_id = parseInt($input.data('line-id'), 10);
var productIDs = [parseInt($input.data('product-id'), 10)];
this._changeCartQuantity($input, value, $dom_optional, line_id, productIDs);
},
/**
* @private
* @param {Event} ev
*/
_onClickSuggestedProduct: function (ev) {
$(ev.currentTarget).prev('input').val(1).trigger('change');
},
/**
* @private
* @param {Event} ev
*/
_onClickSubmit: function (ev) {
if ($(ev.currentTarget).is('#add_to_cart, #products_grid .a-submit')) {
return;
}
var $aSubmit = $(ev.currentTarget);
if (!ev.defaultPrevented && !$aSubmit.is(".disabled")) {
ev.preventDefault();
$aSubmit.closest('form').submit();
}
if ($aSubmit.hasClass('a-submit-disable')) {
$aSubmit.addClass("disabled");
}
if ($aSubmit.hasClass('a-submit-loading')) {
var loading = '<span class="fa fa-cog fa-spin"/>';
var fa_span = $aSubmit.find('span[class*="fa"]');
if (fa_span.length) {
fa_span.replaceWith(loading);
} else {
$aSubmit.append(loading);
}
}
},
/**
* @private
* @param {Event} ev
*/
_onChangeAttribute: function (ev) {
if (!ev.defaultPrevented) {
ev.preventDefault();
const productGrid = this.el.querySelector(".o_wsale_products_grid_table_wrapper");
if (productGrid) {
productGrid.classList.add("opacity-50");
}
$(ev.currentTarget).closest("form").submit();
}
},
/**
* @private
* @param {Event} ev
*/
_onMouseupAddCartLabel: function (ev) { // change price when they are variants
var $label = $(ev.currentTarget);
var $price = $label.parents("form:first").find(".oe_price .oe_currency_value");
if (!$price.data("price")) {
$price.data("price", parseFloat($price.text()));
}
var value = $price.data("price") + parseFloat($label.find(".badge span").text() || 0);
var dec = value % 1;
$price.html(value + (dec < 0.01 ? ".00" : (dec < 1 ? "0" : "") ));
},
/**
* @private
* @param {Event} ev
*/
_onSubmitSaleSearch: function (ev) {
if (!this.$('.dropdown_sorty_by').length) {
return;
}
var $this = $(ev.currentTarget);
if (!ev.defaultPrevented && !$this.is(".disabled")) {
ev.preventDefault();
var oldurl = $this.attr('action');
oldurl += (oldurl.indexOf("?")===-1) ? "?" : "";
if ($this.find('[name=noFuzzy]').val() === "true") {
oldurl += '&noFuzzy=true';
}
var search = $this.find('input.search-query');
window.location = oldurl + '&' + search.attr('name') + '=' + encodeURIComponent(search.val());
}
},
/**
* Toggles the add to cart button depending on the possibility of the
* current combination.
*
* @override
*/
_toggleDisable: function ($parent, isCombinationPossible) {
VariantMixin._toggleDisable.apply(this, arguments);
$parent.find("#add_to_cart").toggleClass('disabled', !isCombinationPossible);
$parent.find(".o_we_buy_now").toggleClass('disabled', !isCombinationPossible);
},
/**
* Write the properties of the form elements in the DOM to prevent the
* current selection from being lost when activating the web editor.
*
* @override
*/
onChangeVariant: function (ev) {
var $component = $(ev.currentTarget).closest('.js_product');
$component.find('input').each(function () {
var $el = $(this);
$el.attr('checked', $el.is(':checked'));
});
$component.find('select option').each(function () {
var $el = $(this);
$el.attr('selected', $el.is(':selected'));
});
this._setUrlHash($component);
return VariantMixin.onChangeVariant.apply(this, arguments);
},
/**
* @private
*/
_onToggleSummary: function () {
$('.toggle_summary_div').toggleClass('d-none');
$('.toggle_summary_div').removeClass('d-xl-block');
},
/**
* @private
*/
_onClickReviewsLink: function () {
$('#o_product_page_reviews_content').collapse('show');
},
/**
* Prevent multiclicks on confirm button when the form is submitted
*
* @private
*/
_onClickConfirmOrder: function () {
// FIXME ANVFE this should only be triggered when we effectively click on that specific button no?
// should not impact the address page at least
const submitFormButton = $('form[name="o_wsale_confirm_order"]').find('button[type="submit"]');
submitFormButton.attr('disabled', true);
setTimeout(() => submitFormButton.attr('disabled', false), 5000);
},
// -------------------------------------
// Utils
// -------------------------------------
/**
* Update the root product during an Add process.
*
* @private
* @param {Object} $form
* @param {Number} productId
* @param {Number} productTemplateId
*/
_updateRootProduct($form, productId, productTemplateId) {
this.rootProduct = {
product_id: productId,
product_template_id: productTemplateId,
quantity: parseFloat($form.find('input[name="add_qty"]').val() || 1),
product_custom_attribute_values: this.getCustomVariantValues($form.find('.js_product')),
variant_values: this.getSelectedVariantValues($form.find('.js_product')),
no_variant_attribute_values: this.getNoVariantAttributeValues($form.find('.js_product'))
};
},
});
publicWidget.registry.WebsiteSale = WebsiteSale
publicWidget.registry.WebsiteSaleLayout = publicWidget.Widget.extend({
selector: '.oe_website_sale',
disabledInEditableMode: false,
events: {
'change .o_wsale_apply_layout input': '_onApplyShopLayoutChange',
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* @private
* @param {Event} ev
*/
_onApplyShopLayoutChange: function (ev) {
const wysiwyg = this.options.wysiwyg;
if (wysiwyg) {
wysiwyg.odooEditor.observerUnactive('_onApplyShopLayoutChange');
}
var clickedValue = $(ev.target).val();
var isList = clickedValue === 'list';
if (!this.editableMode) {
rpc('/shop/save_shop_layout_mode', {
'layout_mode': isList ? 'list' : 'grid',
});
}
const activeClasses = ev.target.parentElement.dataset.activeClasses.split(' ');
ev.target.parentElement.querySelectorAll('.btn').forEach((btn) => {
activeClasses.map(c => btn.classList.toggle(c));
});
var $grid = this.$('#products_grid');
// Disable transition on all list elements, then switch to the new
// layout then reenable all transitions after having forced a redraw
// TODO should probably be improved to allow disabling transitions
// altogether with a class/option.
$grid.find('*').css('transition', 'none');
$grid.toggleClass('o_wsale_layout_list', isList);
void $grid[0].offsetWidth;
$grid.find('*').css('transition', '');
if (wysiwyg) {
wysiwyg.odooEditor.observerActive('_onApplyShopLayoutChange');
}
},
});
publicWidget.registry.WebsiteSaleAccordionProduct = publicWidget.Widget.extend({
selector: "#product_accordion",
/**
* @override
*/
async start() {
await this._super(...arguments);
this._updateAccordionActiveItem();
},
/**
* Replace the .SCSS styling applied awaiting Js for the default bootstrap classes,
* opening the first accordion entry and restoring flush behavior.
*
* @private
*/
_updateAccordionActiveItem() {
const firstAccordionItemEl = this.el.querySelector('.accordion-item');
if (!firstAccordionItemEl) return;
const firstAccordionItemButtonEl = firstAccordionItemEl.querySelector('.accordion-button');
firstAccordionItemButtonEl.classList.remove('collapsed');
firstAccordionItemButtonEl.setAttribute('aria-expanded', 'true');
firstAccordionItemEl.querySelector('.accordion-collapse').classList.add('show');
this.target.classList.remove('o_accordion_not_initialized');
},
});
publicWidget.registry.websiteSaleCarouselProduct = publicWidget.Widget.extend({
selector: '#o-carousel-product',
disabledInEditableMode: false,
events: {
'wheel .o_carousel_product_indicators': '_onMouseWheel',
},
/**
* @override
*/
async start() {
await this._super(...arguments);
this._updateCarouselPosition();
this.throttleOnResize = throttleForAnimation(this._onSlideCarouselProduct.bind(this));
extraMenuUpdateCallbacks.push(this._updateCarouselPosition.bind(this));
if (this.$el.find('.carousel-indicators').length > 0) {
this.$el.on('slide.bs.carousel.carousel_product_slider', this._onSlideCarouselProduct.bind(this));
$(window).on('resize.carousel_product_slider', this.throttleOnResize);
this._updateJustifyContent();
}
},
/**
* @override
*/
destroy() {
this.$el.css('top', '');
this.$el.off('.carousel_product_slider');
if (this.throttleOnResize) {
this.throttleOnResize.cancel();
}
this._super(...arguments);
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* @private
*/
_updateCarouselPosition() {
let size = 5;
for (const el of document.querySelectorAll('.o_top_fixed_element')) {
size += $(el).outerHeight();
}
this.$el.css('top', size);
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* Center the selected indicator to scroll the indicators list when it
* overflows.
*
* @private
* @param {Event} ev
*/
_onSlideCarouselProduct: function (ev) {
const isReversed = this.$el.css('flex-direction') === "column-reverse";
const isLeftIndicators = this.$el.hasClass('o_carousel_product_left_indicators');
const $indicatorsDiv = isLeftIndicators ? this.$el.find('.o_carousel_product_indicators') : this.$el.find('.carousel-indicators');
let indicatorIndex = $(ev.relatedTarget).index();
indicatorIndex = indicatorIndex > -1 ? indicatorIndex : this.$el.find('li.active').index();
const $indicator = $indicatorsDiv.find('[data-bs-slide-to=' + indicatorIndex + ']');
const indicatorsDivSize = isLeftIndicators && !isReversed ? $indicatorsDiv.outerHeight() : $indicatorsDiv.outerWidth();
const indicatorSize = isLeftIndicators && !isReversed ? $indicator.outerHeight() : $indicator.outerWidth();
const indicatorPosition = isLeftIndicators && !isReversed ? $indicator.position().top : $indicator.position().left;
const scrollSize = isLeftIndicators && !isReversed ? $indicatorsDiv[0].scrollHeight : $indicatorsDiv[0].scrollWidth;
let indicatorsPositionDiff = (indicatorPosition + (indicatorSize/2)) - (indicatorsDivSize/2);
indicatorsPositionDiff = Math.min(indicatorsPositionDiff, scrollSize - indicatorsDivSize);
this._updateJustifyContent();
const indicatorsPositionX = isLeftIndicators && !isReversed ? '0' : '-' + indicatorsPositionDiff;
const indicatorsPositionY = isLeftIndicators && !isReversed ? '-' + indicatorsPositionDiff : '0';
const translate3D = indicatorsPositionDiff > 0 ? "translate3d(" + indicatorsPositionX + "px," + indicatorsPositionY + "px,0)" : '';
$indicatorsDiv.css("transform", translate3D);
},
/**
* @private
*/
_updateJustifyContent: function () {
const $indicatorsDiv = this.$el.find('.carousel-indicators');
$indicatorsDiv.css('justify-content', 'start');
if (uiUtils.getSize() <= SIZES.MD) {
if (($indicatorsDiv.children().last().position().left + this.$el.find('li').outerWidth()) < $indicatorsDiv.outerWidth()) {
$indicatorsDiv.css('justify-content', 'center');
}
}
},
/**
* @private
* @param {Event} ev
*/
_onMouseWheel: function (ev) {
ev.preventDefault();
if (ev.originalEvent.deltaY > 0) {
this.$el.carousel('next');
} else {
this.$el.carousel('prev');
}
},
});
publicWidget.registry.websiteSaleProductPageReviews = publicWidget.Widget.extend({
selector: '#o_product_page_reviews',
disabledInEditableMode: false,
/**
* @override
*/
async start() {
await this._super(...arguments);
this._updateChatterComposerPosition();
extraMenuUpdateCallbacks.push(this._updateChatterComposerPosition.bind(this));
},
/**
* @override
*/
destroy() {
this.$el.find('.o_portal_chatter_composer').css('top', '');
this._super(...arguments);
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* @private
*/
_updateChatterComposerPosition() {
let size = 20;
for (const el of document.querySelectorAll('.o_top_fixed_element')) {
size += $(el).outerHeight();
}
this.$el.find('.o_portal_chatter_composer').css('top', size);
},
});
export default {
WebsiteSale: publicWidget.registry.WebsiteSale,
WebsiteSaleLayout: publicWidget.registry.WebsiteSaleLayout,
WebsiteSaleProductPage: publicWidget.registry.WebsiteSaleAccordionProduct,
WebsiteSaleCarouselProduct: publicWidget.registry.websiteSaleCarouselProduct,
WebsiteSaleProductPageReviews: publicWidget.registry.websiteSaleProductPageReviews,
};