884 lines
33 KiB
JavaScript
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,
|
|
};
|