[IMP] product,(website_)sale: Product configurator bug fix

Targets commit d3530eb07e

Purpose
=======

[Follow up JCR testing]

Various bug fixes including:

- optional products dont show when they have variants configured on them
- when no variant is created, you cant choose the product one first time in the ecommerce
- when in debug, attribute and attribute value menu is double
- fix design of default picture in cart summary / product comparison
- fix missing href tag on product comparison "add to cart" buttons
- products with the "Can be sold" box unchecked should not appear in the product configurator
This commit is contained in:
Aurélien Warnon
2018-10-01 13:29:09 +02:00
parent 27a7393852
commit 154fc7d9dd
22 changed files with 215 additions and 129 deletions
@@ -91,6 +91,7 @@ var SectionAndNoteListRenderer = ListRenderer.extend({
this.unselectRow().then(function () {
var context = ev.currentTarget.dataset.context;
var pricelistId = self._getPricelistId();
if (context && pyUtils.py_eval(context).open_product_configurator){
self._rpc({
model: 'ir.model.data',
@@ -102,7 +103,10 @@ var SectionAndNoteListRenderer = ListRenderer.extend({
type: 'ir.actions.act_window',
res_model: 'sale.product.configurator',
views: [[res_id, 'form']],
target: 'new'
target: 'new',
context: {
'default_pricelist_id': pricelistId
}
}, {
on_close: function (products) {
if (products && products !== 'special'){
@@ -124,6 +128,20 @@ var SectionAndNoteListRenderer = ListRenderer.extend({
});
},
/**
* Will try to get the pricelist_id value from the parent sale_order form
*
* @private
* @returns {integer} pricelist_id's id
*/
_getPricelistId: function () {
var saleOrderForm = this.getParent() && this.getParent().getParent();
var stateData = saleOrderForm && saleOrderForm.state && saleOrderForm.state.data;
var pricelist_id = stateData.pricelist_id && stateData.pricelist_id.data;
return pricelist_id.id;
},
/**
* Will map the products to appropriate record objects that are
* ready for the default_get
+1 -1
View File
@@ -11,7 +11,7 @@
<header>
<button string="Configure Variants" type="action"
name="%(product_attribute_value_action)d"
attrs="{'invisible': [('attribute_line_ids', '=', False)]}"
attrs="{'invisible': [('attribute_line_ids', '&lt;=', 0)]}"
groups="product.group_product_variant"/>
</header>
<sheet>
@@ -60,7 +60,7 @@ class ProductConfiguratorController(http.Controller):
has_optional_products = False
for optional_product in product.optional_product_ids:
if optional_product.get_filtered_variants(product):
if optional_product.has_dynamic_attributes() or optional_product.get_filtered_variants(product):
has_optional_products = True
break
-1
View File
@@ -159,7 +159,6 @@ class SaleOrder(models.Model):
partner_shipping_id = fields.Many2one('res.partner', string='Delivery Address', readonly=True, required=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)], 'sale': [('readonly', False)]}, help="Delivery address for current sales order.")
pricelist_id = fields.Many2one('product.pricelist', string='Pricelist', required=True, readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, help="Pricelist for current sales order.")
pricelist_id_id = fields.Integer(string="Pricelist ID", related='pricelist_id.id', readonly=True)
currency_id = fields.Many2one("res.currency", related='pricelist_id.currency_id', string="Currency", readonly=True, required=True)
analytic_account_id = fields.Many2one('account.analytic.account', 'Analytic Account', readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, help="The analytic account related to a sales order.", copy=False, oldname='project_id')
@@ -56,7 +56,7 @@ var ProductConfiguratorFormController = FormController.extend({
route: '/product_configurator/configure',
params: {
product_id: event.data.changes.product_template_id.id,
pricelist_id: $('.js_sale_order_pricelist_id').html()
pricelist_id: this.renderer.pricelistId
}
}).then(function (configurator) {
self.renderer.renderConfigurator(configurator);
@@ -87,19 +87,12 @@ var ProductConfiguratorFormController = FormController.extend({
];
var productId = parseInt($modal.find(productSelector.join(', ')).first().val(), 10);
var productReady = $.Deferred();
if (productId){
productReady.resolve(productId);
} else {
productReady = this._rpc({
model: 'product.template',
method: 'create_product_variant',
args: [
$modal.find('.product_template_id').val(),
JSON.stringify(self.renderer.getSelectedVariantValues($modal))
],
});
}
var productReady = this.renderer.selectOrCreateProduct(
$modal,
productId,
$modal.find('.product_template_id').val(),
false
);
productReady.done(function (productId){
$modal.find(productSelector.join(', ')).val(productId);
@@ -126,7 +119,7 @@ var ProductConfiguratorFormController = FormController.extend({
self.optionalProductsModal = new OptionalProductsModal($('body'), {
rootProduct: self.rootProduct,
pricelistId: $('.js_sale_order_pricelist_id').html(),
pricelistId: self.renderer.pricelistId,
okButtonText: _t('Confirm'),
cancelButtonText: _t('Back'),
title: _t('Configure')
@@ -269,6 +269,43 @@ var ProductConfiguratorMixin = {
return values.concat(unchangedValues);
},
/**
* Will return a deferred:
* - If the product already exists, immediately resolves it with the product_id
* - If the product does not exist yet ("dynamic" variant creation), this method will
* create the product first and then resolve the deferred with the created product's id
*
* @param {$.Element} $container the container to look into
* @param {integer} productId the product id
* @param {integer} productTemplateId the corresponding product template id
* @param {boolean} useAjax wether the rpc call should be done using ajax.jsonRpc or using _rpc
* @returns {$.Deferred} the deferred that will be resolved with a {integer} productId
*/
selectOrCreateProduct: function ($container, productId, productTemplateId, useAjax) {
var self = this;
var productReady = $.Deferred();
if (productId && productId !== '0'){
productReady.resolve(productId);
} else {
var params = {
model: 'product.template',
method: 'create_product_variant',
args: [
productTemplateId,
JSON.stringify(self.getSelectedVariantValues($container))
]
};
if (useAjax) {
productReady = ajax.jsonRpc('/web/dataset/call', 'call', params);
} else {
productReady = this._rpc(params);
}
}
return productReady;
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
@@ -345,20 +345,12 @@ var OptionalProductsModal = Dialog.extend(ServicesMixin, ProductConfiguratorMixi
$main_product.after($parent);
}
var productId = $parent.find('.product_id').val();
var productReady = $.Deferred();
if (productId && productId !== '0'){
productReady.resolve(productId);
} else {
productReady = ajax.jsonRpc('/web/dataset/call', 'call', {
model: 'product.template',
method: 'create_product_variant',
args: [
productTemplateId,
JSON.stringify(self.getSelectedVariantValues($parent))
]
});
}
var productReady = this.selectOrCreateProduct(
$parent,
$parent.find('.product_id').val(),
productTemplateId,
true
);
productReady.done(function (productId){
$parent.find('.product_id').val(productId);
@@ -376,9 +368,12 @@ var OptionalProductsModal = Dialog.extend(ServicesMixin, ProductConfiguratorMixi
if ($addedItem.find(".product_template_id").length > 0) {
// we need to map this product with it's optional products to be able to:
// - remove them from the cart if the parent product is removed
// - place the optional products under their parent in the interface when they're added to the cart
optionalProductsMap[productTemplateId] = $addedItem.find(".product_template_id").map(function () {
return $(this).val();
// - place the optional products under their parent in
// the interface when they're added to the cart
optionalProductsMap[productTemplateId] = $addedItem
.find(".product_template_id")
.map(function () {
return $(this).val();
}).get();
}
@@ -10,8 +10,7 @@ var ProductConfiguratorFormRenderer = FormRenderer.extend(ProductConfiguratorMix
*/
init: function (){
this._super.apply(this, arguments);
this.pricelistId = $('.js_sale_order_pricelist_id').html();
this.pricelistId = this.state.context.default_pricelist_id || 0;
},
/**
* @override
@@ -4,20 +4,20 @@ odoo.define('sale.product.configurator.tests', function (require) {
var FormView = require('web.FormView');
var ProductConfiguratorFormView = require('sale.ProductConfiguratorFormView');
var testUtils = require('web.test_utils');
var ajax = require('web.ajax');
var createView = testUtils.createView;
var getArch = function (){
return '<form>' +
'<sheet>' +
'<field name="sale_order_line">' +
'<field name="pricelist_id" widget="selection" />' +
'<field name="sale_order_line" widget="section_and_note_one2many">' +
'<tree editable="top"><control>' +
'<create string="Add a product"/>' +
'<create string="Configure a product" context="{\'open_product_configurator\': \'true\'}"/>' +
'<create string="Configure a product" context="{\'open_product_configurator\': True}"/>' +
'<create string="Add a section" context="{\'default_display_type\': \'line_section\'}"/>' +
'<create string="Add a note" context="{\'default_display_type\': \'line_note\'}"/>' +
'</control>' +
'<field name="product_id"/><field name="quantity"/>' +
'<field name="product_id"/><field name="product_uom_qty"/>' +
'</tree>' +
'</field>' +
'</sheet>' +
@@ -39,11 +39,23 @@ odoo.define('sale.product.configurator.tests', function (require) {
product: {
fields: {
id: {type: 'integer'}
}
},
records: [{
id: 1,
display_name: "Customizable Desk (1)"
}, {
id: 2,
display_name: "Customizable Desk (2)"
}]
},
sale_order: {
fields: {
id: {type: 'integer'},
pricelist_id: {
string: 'Pricelist',
type: 'one2many',
relation: 'pricelist'
},
sale_order_line: {
string: 'lines',
type: 'one2many',
@@ -58,7 +70,7 @@ odoo.define('sale.product.configurator.tests', function (require) {
type: 'many2one',
relation: 'product'
},
quantity: {type: 'integer'}
product_uom_qty: {type: 'integer'}
}
},
sale_product_configurator: {
@@ -73,6 +85,11 @@ odoo.define('sale.product.configurator.tests', function (require) {
product_template_id: 42
}]
},
pricelist: {
fields: {
id: {type: 'integer'}
}
}
};
}
}, function (){
@@ -108,10 +125,10 @@ odoo.define('sale.product.configurator.tests', function (require) {
arch: getArch()
});
var list = form.renderer.allFieldWidgets[form.handle][0];
var list = form.renderer.allFieldWidgets[form.handle][1];
list.trigger_up('add_record', {
context: [{product_id: 1, quantity: 2}, {product_id: 2, quantity: 3}],
context: [{default_product_id: 1, default_product_uom_qty: 2}, {default_product_id: 2, default_product_uom_qty: 3}],
forceEditable: "bottom" ,
allowWarning: true
});
@@ -144,9 +161,6 @@ odoo.define('sale.product.configurator.tests', function (require) {
return this._super.apply(this, arguments);
}
});
var div = '<div>';
var $div = $(div).addClass('js_sale_order_pricelist_id').html(43);
product_configurator_form.$('.o_input').after($div);
product_configurator_form.$('.o_input').click();
$("ul.ui-autocomplete li a:contains('Customizable Desk')").mouseenter().click();
});
@@ -122,7 +122,7 @@
<template id="optional_product_items">
<t t-foreach="product.optional_product_ids" t-as="product">
<t t-if="len(product.get_filtered_variants(reference_product)) > 0">
<t t-if="len(product.get_filtered_variants(reference_product)) > 0 or product.has_dynamic_attributes()">
<tr class="js_product">
<td class="td-img">
<input type="hidden" class="product_template_id" t-att-value="product.id"/>
-13
View File
@@ -94,18 +94,6 @@
<menuitem id="menu_product" name="Product Variants" parent="product_menu_catalog" sequence="2" groups="base.group_no_one" active="False"/>
<menuitem action="product_template_action" id="menu_product_template_action" parent="product_menu_catalog" sequence="1" active="False"/>
<menuitem id="prod_config_main" name="Products" parent="menu_sale_config" sequence="5"/>
<menuitem id="menu_attribute_action" action="product.attribute_action" parent="prod_config_main" sequence="1" groups="base.group_no_one" active="False"/>
<menuitem id="menu_product_category_config_sale"
action="product.product_category_action_form"
parent="sale.prod_config_main"
sequence="1"
groups="base.group_no_one"/>
<menuitem action="product.variants_action" id="menu_variants_action" parent="sale.prod_config_main" sequence="2" groups="base.group_no_one" active="False"/>
<menuitem id="menu_product_category_config_sale"
action="product.product_category_action_form"
parent="sale.prod_config_main"
sequence="1"
groups="base.group_no_one"/>
<menuitem id="menu_products" action="product.product_normal_action_sell" parent="product_menu_catalog" groups="product.group_product_variant" sequence="2" active="False"/>
<menuitem id="next_id_16" name="Units of Measure" parent="sale.prod_config_main" sequence="6" groups="uom.group_uom" active="False"/>
<menuitem action="uom.product_uom_form_action" id="menu_product_uom_form_action" parent="sale.prod_config_main" sequence="7" groups="uom.group_uom" active="False"/>
@@ -290,7 +278,6 @@
<field name="validity_date" attrs="{'invisible': [('state', 'in', ['sale', 'done'])]}"/>
<field name="confirmation_date" attrs="{'invisible': [('state', 'in', ['draft', 'sent', 'cancel'])]}"/>
<field name="pricelist_id" groups="product.group_sale_pricelist"/>
<field name="pricelist_id_id" invisible="1" class="js_sale_order_pricelist_id" />
<field name="currency_id" invisible="1"/>
<field name="payment_term_id" options="{'no_create': True}"/>
</group>
@@ -10,4 +10,5 @@ class SaleProductConfigurator(models.TransientModel):
product_template_id = fields.Many2one(
'product.template', string="Product",
required=True, domain=['|', ('attribute_line_ids', '!=', False), ('optional_product_ids', '!=', False)])
required=True, domain=[('sale_ok', '=', True), '|', ('attribute_line_ids', '!=', False), ('optional_product_ids', '!=', False)])
pricelist_id = fields.Many2one('product.pricelist', 'Pricelist', readonly=True)
@@ -21,12 +21,6 @@
<record id="sale.prod_config_main" model="ir.ui.menu">
<field name="active" eval="True"/>
</record>
<record id="sale.menu_attribute_action" model="ir.ui.menu">
<field name="active" eval="True"/>
</record>
<record id="sale.menu_variants_action" model="ir.ui.menu">
<field name="active" eval="True"/>
</record>
<record id="sale.menu_products" model="ir.ui.menu">
<field name="active" eval="True"/>
</record>
+8 -2
View File
@@ -394,11 +394,15 @@ class WebsiteSale(ProductConfiguratorController):
@http.route(['/shop/cart/update'], type='http', auth="public", methods=['POST'], website=True, csrf=False)
def cart_update(self, product_id, add_qty=1, set_qty=0, **kw):
product_custom_attribute_values = None
if kw.get('product_custom_attribute_values'):
product_custom_attribute_values = json.loads(kw.get('product_custom_attribute_values'))
request.website.sale_get_order(force_create=1)._cart_update(
product_id=int(product_id),
add_qty=add_qty,
set_qty=set_qty,
product_custom_attribute_values=json.loads(kw.get('product_custom_attribute_values', '{}'))
product_custom_attribute_values=product_custom_attribute_values
)
return request.redirect("/shop/cart")
@@ -1078,7 +1082,9 @@ class WebsiteSale(ProductConfiguratorController):
if "optional-product-" in k and int(kw.get(k.replace("product", "add"))):
optional_product_ids.append(int(v))
custom_values = json.loads(kw.get('custom_values'))
custom_values = []
if kw.get('custom_values'):
custom_values = json.loads(kw.get('custom_values'))
value = {}
if add_qty or set_qty:
@@ -62,21 +62,12 @@ sAnimations.registry.WebsiteSaleOptions = sAnimations.Class.extend(ProductConfig
'input[type="radio"][name="product_id"]:checked'
];
var productId = parseInt($form.find(productSelector.join(', ')).first().val(), 10);
var productReady = $.Deferred();
if (productId){
productReady.resolve(productId);
} else {
productReady = self._rpc({
model: 'product.template',
method: 'create_product_variant',
args: [
$form.find('.product_template_id').val(),
JSON.stringify(self.getSelectedVariantValues($form))
],
});
}
var productReady = this.selectOrCreateProduct(
$form,
parseInt($form.find(productSelector.join(', ')).first().val(), 10),
$form.find('.product_template_id').val(),
false
);
productReady.done(function (productId){
$form.find(productSelector.join(', ')).val(productId);
@@ -138,3 +138,8 @@ $o-wsale-wizard-label-completed: $success;
}
}
}
table.js_cart_lines .td-img img {
// allows sizing the placeholder image to the "image_small" size of 64px
max-height: 64px;
}
+4 -4
View File
@@ -121,7 +121,7 @@
</h6>
<div itemprop="offers" itemscope="itemscope" itemtype="http://schema.org/Offer" class="product_price" t-if="product.product_variant_ids">
<b>
<t t-if="product.website_price_difference and website.get_current_pricelist().discount_policy=='without_discount'">
<t t-if="product.product_variant_id and product.website_price_difference and website.get_current_pricelist().discount_policy=='without_discount'">
<del class="text-danger mr8" style="white-space: nowrap;" t-esc="compute_currency(product.website_public_price)" t-options="{'widget': 'monetary', 'display_currency': website.get_current_pricelist().currency_id, 'from_currency': website.currency_id}" />
</t>
@@ -484,7 +484,7 @@
<span itemprop="url" style="display:none;" t-esc="'%sshop/product/%s' % (request.httprequest.url_root, slug(product))"/>
<form action="/shop/cart/update" method="POST">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()" />
<div class="js_product js_main_product" t-if="product.product_variant_ids">
<div class="js_product js_main_product">
<t t-placeholder="select">
<input type="hidden" class="product_id" name="product_id" t-att-value="product.product_variant_id.id if len(product.product_variant_ids) == 1 else '0'" />
<input type="hidden" class="product_template_id" name="product_template_id" t-att-value="product.id" />
@@ -604,7 +604,7 @@
<div itemprop="offers" itemscope="itemscope" itemtype="http://schema.org/Offer" class="product_price mt16">
<h4 class="oe_price_h4 css_editable_mode_hidden">
<span class="text-danger oe_default_price" style="text-decoration: line-through; white-space: nowrap;"
t-esc="compute_currency(product.website_public_price)" t-options="{'widget': 'monetary', 'display_currency': website.get_current_pricelist().currency_id, 'from_currency': website.currency_id}" t-att-style="'text-decoration: line-through; white-space: nowrap; ' + '' if product.website_price_difference and website.get_current_pricelist().discount_policy == 'without_discount' else 'display: none;'"
t-esc="compute_currency(product.website_public_price)" t-options="{'widget': 'monetary', 'display_currency': website.get_current_pricelist().currency_id, 'from_currency': website.currency_id}" t-att-style="'text-decoration: line-through; white-space: nowrap; ' + '' if product.product_variant_id and product.website_price_difference and website.get_current_pricelist().discount_policy == 'without_discount' else 'display: none;'"
/>
<b class="oe_price" style="white-space: nowrap;" t-esc="product.website_price" t-options="{'widget': 'monetary', 'display_currency': website.currency_id}"/>
<span itemprop="price" style="display:none;" t-esc="product.website_price"/>
@@ -951,7 +951,7 @@
<div class="text-muted d-none d-md-block" t-field="product.description_sale" />
</td>
<td class='td-price'>
<t t-if="product.website_price_difference and website.get_current_pricelist().discount_policy=='without_discount'">
<t t-if="product.product_variant_id and product.website_price_difference and website.get_current_pricelist().discount_policy=='without_discount'">
<del class="text-danger mr8" style="white-space: nowrap;" t-field="product.lst_price" t-options="{'widget': 'monetary','from_currency': product.currency_id, 'display_currency': website.currency_id}" />
</t>
<span t-field="product.website_price" style="white-space: nowrap;" t-options="{'widget': 'monetary','display_currency': website.currency_id}" />
@@ -4,13 +4,17 @@ odoo.define('website_sale_comparison.comparison', function (require) {
var core = require('web.core');
var utils = require('web.utils');
var Widget = require('web.Widget');
var ProductConfiguratorMixin = require('sale.ProductConfiguratorMixin');
var sAnimations = require('website.content.snippets.animation');
var website_sale_utils = require('website_sale.utils');
var qweb = core.qweb;
var _t = core._t;
var ProductComparison = Widget.extend({
// ProductConfiguratorMixin events are overridden on purpose here
// to avoid registering them more than once since they are already registered
// in website_sale.js
var ProductComparison = Widget.extend(ProductConfiguratorMixin, {
xmlDependencies: ['/website_sale_comparison/static/src/xml/comparison.xml'],
template: 'product_comparison_template',
@@ -82,20 +86,38 @@ var ProductComparison = Widget.extend({
* @param {jQuery} $elem
*/
handleCompareAddition: function ($elem) {
var self = this;
if (this.comparelist_product_ids.length < this.product_compare_limit) {
var prod = $elem.data('product-product-id');
var productId = $elem.data('product-product-id');
if ($elem.hasClass('o_add_compare_dyn')) {
prod = $elem.parent().find('.product_id').val();
if (!prod) { // case List View Variants
prod = $elem.parent().find('input:checked').first().val();
productId = $elem.parent().find('.product_id').val();
if (!productId) { // case List View Variants
productId = $elem.parent().find('input:checked').first().val();
}
prod = parseInt(prod, 10);
}
if (!prod) {
return;
}
this._addNewProducts(prod);
website_sale_utils.animateClone($('#comparelist .o_product_panel_header'), $elem.closest('form'), -50, 10);
var productReady = this.selectOrCreateProduct(
$elem.closest('form'),
productId,
$elem.closest('form').find('.product_template_id').val(),
false
);
productReady.done(function (productId) {
productId = parseInt(productId, 10);
if (!productId) {
return;
}
self._addNewProducts(productId);
website_sale_utils.animateClone(
$('#comparelist .o_product_panel_header'),
$elem.closest('form'),
-50,
10
);
});
} else {
this.$('.o_comparelist_limit_warning').show();
$('#comparelist .o_product_panel_header').popover('show');
@@ -10,14 +10,14 @@
<template id="add_to_compare" inherit_id="website_sale.products_item" active="True" customize_show="True" name="Comparison List" priority="22">
<xpath expr="//div[hasclass('product_price')]" position="inside">
<button type="button" role="button" class="d-none d-md-inline-block btn btn-secondary btn-sm o_add_compare" title="Compare" aria-label="Compare" t-att-data-product-product-id="product.product_variant_ids[0].id" data-action="o_comparelist"><span class="fa fa-exchange"></span></button>
<button type="button" role="button" class="d-none d-md-inline-block btn btn-secondary btn-sm o_add_compare" title="Compare" aria-label="Compare" t-att-data-product-product-id="product.product_variant_ids[0].id if product.product_variant_ids else '0'" data-action="o_comparelist"><span class="fa fa-exchange"></span></button>
</xpath>
</template>
<template id="product_add_to_compare" name='Add to comparison in product page' inherit_id="website_sale.product" priority="8">
<xpath expr="//a[@id='add_to_cart']" position="after">
<t t-if="product.env.ref('website_sale_comparison.add_to_compare').active">
<button type="button" role="button" class="d-none d-md-inline-block btn btn-secondary btn-lg mt8 o_add_compare_dyn" title="Compare" aria-label="Compare" t-att-data-product-product-id="product.product_variant_ids[0].id" data-action="o_comparelist"><span class="fa fa-exchange"></span></button>
<button type="button" role="button" class="d-none d-md-inline-block btn btn-secondary btn-lg mt8 o_add_compare_dyn" title="Compare" aria-label="Compare" t-att-data-product-product-id="product.product_variant_ids[0].id if product.product_variant_ids else '0'" data-action="o_comparelist"><span class="fa fa-exchange"></span></button>
</t>
</xpath>
</template>
@@ -3,17 +3,22 @@ odoo.define('website_sale_wishlist.wishlist', function (require) {
var sAnimations = require('website.content.snippets.animation');
var wSaleUtils = require('website_sale.utils');
var ProductConfiguratorMixin = require('sale.ProductConfiguratorMixin');
sAnimations.registry.ProductWishlist = sAnimations.Class.extend({
// ProductConfiguratorMixin events are overridden on purpose here
// to avoid registering them more than once since they are already registered
// in website_sale.js
sAnimations.registry.ProductWishlist = sAnimations.Class.extend(ProductConfiguratorMixin, {
selector: '.oe_website_sale',
read_events: {
'click #my_wish': '_onClickMyWish',
'click .o_add_wishlist, .o_add_wishlist_dyn': '_onClickAddWish',
'change input.js_variant_change, select.js_variant_change, ul[data-attribute_exclusions]': '_onChangeVariant',
'change input.product_id': '_onChangeVariant',
'change input.js_product_change': '_onChangeProduct',
'click .wishlist-section .o_wish_rm': '_onClickWishRemove',
'click .wishlist-section .o_wish_add': '_onClickWishAdd',
},
events: sAnimations.Class.events,
start: function () {
var self = this;
@@ -48,23 +53,39 @@ sAnimations.registry.ProductWishlist = sAnimations.Class.extend({
*/
_addNewProducts: function ($el) {
var self = this;
var productID = parseInt($el.data('product-product-id'), 10);
if (!productID && $el.hasClass('o_add_wishlist_dyn')) {
productID = parseInt($el.parent().find('.product_id').val());
}
if (productID && !_.contains(self.wishlistProductIDs, productID)) {
return this._rpc({
route: '/shop/wishlist/add',
params: {
product_id: productID,
},
}).then(function () {
self.wishlistProductIDs.push(productID);
self._updateWishlistView();
wSaleUtils.animateClone($('#my_wish'), $el.closest('form'), 25, 40);
$el.prop("disabled", true).addClass('disabled');
});
var productID = $el.data('product-product-id');
if ($el.hasClass('o_add_wishlist_dyn')) {
productID = $el.parent().find('.product_id').val();
if (!productID) { // case List View Variants
productID = $el.parent().find('input:checked').first().val();
}
productID = parseInt(productID, 10);
}
var productReady = this.selectOrCreateProduct(
$el.closest('form'),
productID,
$el.closest('form').find('.product_template_id').val(),
false
);
productReady.done(function (productId) {
productId = parseInt(productId, 10);
if (productId && !_.contains(self.wishlistProductIDs, productId)) {
return self._rpc({
route: '/shop/wishlist/add',
params: {
product_id: productId,
},
}).then(function () {
self.wishlistProductIDs.push(productId);
self._updateWishlistView();
wSaleUtils.animateClone($('#my_wish'), $el.closest('form'), 25, 40);
$el.prop("disabled", true).addClass('disabled');
});
}
});
},
/**
* @private
@@ -175,16 +196,15 @@ sAnimations.registry.ProductWishlist = sAnimations.Class.extend({
* @param {Event} ev
*/
_onChangeVariant: function (ev) {
var $ul = $(ev.target).closest('.js_add_cart_variants');
var $parent = $ul.closest('.js_product');
var $productID = $parent.find('.product_id').first();
var $input = $(ev.target);
var $parent = $input.closest('.js_product');
var $el = $parent.find("[data-action='o_wishlist']");
if (!_.contains(this.wishlistProductIDs, parseInt($productID.val(), 10))) {
if (!_.contains(this.wishlistProductIDs, parseInt($input.val(), 10))) {
$el.prop("disabled", false).removeClass('disabled').removeAttr('disabled');
} else {
$el.prop("disabled", true).addClass('disabled').attr('disabled', 'disabled');
}
$el.data('product-product-id', parseInt($productID.val(), 10));
$el.data('product-product-id', parseInt($input.val(), 10));
},
/**
* @private
@@ -15,4 +15,9 @@
width: 100px;
}
}
}
table.table-comparator .td-img img {
// allows sizing the placeholder image to the "image" size of 100px
max-height: 100px;
}
@@ -12,7 +12,7 @@
<template id="add_to_wishlist" inherit_id="website_sale.products_item" active="True" customize_show="True" name="Wishlist" priority="20">
<xpath expr="//div[hasclass('product_price')]" position="inside">
<t t-set="in_wish" t-value="product.product_variant_ids &amp; request.env['product.wishlist'].current().mapped('product_id')"/>
<button type="button" role="button" class="btn btn-secondary btn-sm o_add_wishlist" t-att-disabled='in_wish or None' title="Add to Wishlist" t-att-data-product-product-id="product.product_variant_ids[0].id" data-action="o_wishlist"><span class="fa fa-heart" role="img" aria-label="Add to wishlist"></span></button>
<button type="button" role="button" class="btn btn-secondary btn-sm o_add_wishlist" t-att-disabled='in_wish or None' title="Add to Wishlist" t-att-data-product-product-id="product.product_variant_ids[0].id if product.product_variant_ids else '0'" data-action="o_wishlist"><span class="fa fa-heart" role="img" aria-label="Add to wishlist"></span></button>
</xpath>
</template>
@@ -20,7 +20,7 @@
<xpath expr="//a[@id='add_to_cart']" position="after">
<t t-if="product.env.ref('website_sale_wishlist.add_to_wishlist').active">
<t t-set="in_wish" t-value="product.product_variant_ids &amp; request.env['product.wishlist'].current().mapped('product_id')"/>
<button type="button" role="button" class="btn btn-secondary btn-lg mt8 o_add_wishlist_dyn" t-att-disabled='in_wish or None' title="Add to Wishlist" t-att-data-product-product-id="product.product_variant_ids[0].id" data-action="o_wishlist"><span class="fa fa-heart" role="img" aria-label="Add to wishlist"></span></button>
<button type="button" role="button" class="btn btn-secondary btn-lg mt8 o_add_wishlist_dyn" t-att-disabled='in_wish or None' title="Add to Wishlist" t-att-data-product-product-id="product.product_variant_ids[0].id if product.product_variant_ids else '0'" data-action="o_wishlist"><span class="fa fa-heart" role="img" aria-label="Add to wishlist"></span></button>
</t>
</xpath>
</template>