Commit 92ef4810 by lmf

优化点击色块更换主图和索璐图

parent b36441fc
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
require([
'jquery'
], function ($) {
'use strict';
/**
* Add selected swatch attributes to redirect url
*
* @see Magento_Catalog/js/catalog-add-to-cart
*/
$('body').on('catalogCategoryAddToCartRedirect', function (event, data) {
$(data.form).find('[name*="super"]').each(function (index, item) {
var $item = $(item),
attr;
if ($item.attr('data-attr-name')) {
attr = $item.attr('data-attr-name');
} else {
attr = $item.parent().attr('attribute-code');
}
data.redirectParameters.push(attr + '=' + $item.val());
});
});
});
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
define([
'jquery',
'underscore',
'mage/template',
'mage/smart-keyboard-handler',
'mage/translate',
'priceUtils',
'jquery-ui-modules/widget',
'jquery/jquery.parsequery',
'mage/validation/validation'
], function ($, _, mageTemplate, keyboardHandler, $t, priceUtils) {
'use strict';
/**
* Extend form validation to support swatch accessibility
*/
$.widget('mage.validation', $.mage.validation, {
/**
* Handle form with swatches validation. Focus on first invalid swatch block.
*
* @param {jQuery.Event} event
* @param {Object} validation
*/
listenFormValidateHandler: function (event, validation) {
var swatchWrapper, firstActive, swatches, swatch, successList, errorList, firstSwatch;
this._superApply(arguments);
swatchWrapper = '.swatch-attribute-options';
swatches = $(event.target).find(swatchWrapper);
if (!swatches.length) {
return;
}
swatch = '.swatch-attribute';
firstActive = $(validation.errorList[0].element || []);
successList = validation.successList;
errorList = validation.errorList;
firstSwatch = $(firstActive).parent(swatch).find(swatchWrapper);
keyboardHandler.focus(swatches);
$.each(successList, function (index, item) {
$(item).parent(swatch).find(swatchWrapper).attr('aria-invalid', false);
});
$.each(errorList, function (index, item) {
$(item.element).parent(swatch).find(swatchWrapper).attr('aria-invalid', true);
});
if (firstSwatch.length) {
$(firstSwatch).focus();
}
}
});
/**
* Render tooltips by attributes (only to up).
* Required element attributes:
* - data-option-type (integer, 0-3)
* - data-option-label (string)
* - data-option-tooltip-thumb
* - data-option-tooltip-value
* - data-thumb-width
* - data-thumb-height
*/
$.widget('mage.SwatchRendererTooltip', {
options: {
delay: 200, //how much ms before tooltip to show
tooltipClass: 'swatch-option-tooltip' //configurable, but remember about css
},
/**
* @private
*/
_init: function () {
var $widget = this,
$this = this.element,
$element = $('.' + $widget.options.tooltipClass),
timer,
type = parseInt($this.data('option-type'), 10),
label = $this.data('option-label'),
thumb = $this.data('option-tooltip-thumb'),
value = $this.data('option-tooltip-value'),
width = $this.data('thumb-width'),
height = $this.data('thumb-height'),
$image,
$title,
$corner;
if (!$element.length) {
$element = $('<div class="' +
$widget.options.tooltipClass +
'"><div class="image"></div><div class="title"></div><div class="corner"></div></div>'
);
$('body').append($element);
}
$image = $element.find('.image');
$title = $element.find('.title');
$corner = $element.find('.corner');
$this.hover(function () {
if (!$this.hasClass('disabled')) {
timer = setTimeout(
function () {
var leftOpt = null,
leftCorner = 0,
left,
$window;
if (type === 2) {
// Image
$image.css({
'background': 'url("' + thumb + '") no-repeat center', //Background case
'background-size': 'initial',
'width': width + 'px',
'height': height + 'px'
});
$image.show();
} else if (type === 1) {
// Color
$image.css({
background: value
});
$image.show();
} else if (type === 0 || type === 3) {
// Default
$image.hide();
}
$title.text(label);
leftOpt = $this.offset().left;
left = leftOpt + $this.width() / 2 - $element.width() / 2;
$window = $(window);
// the numbers (5 and 5) is magick constants for offset from left or right page
if (left < 0) {
left = 5;
} else if (left + $element.width() > $window.width()) {
left = $window.width() - $element.width() - 5;
}
// the numbers (6, 3 and 18) is magick constants for offset tooltip
leftCorner = 0;
if ($element.width() < $this.width()) {
leftCorner = $element.width() / 2 - 3;
} else {
leftCorner = (leftOpt > left ? leftOpt - left : left - leftOpt) + $this.width() / 2 - 6;
}
$corner.css({
left: leftCorner
});
$element.css({
left: left,
top: $this.offset().top - $element.height() - $corner.height() - 18
}).show();
},
$widget.options.delay
);
}
}, function () {
$element.hide();
clearTimeout(timer);
});
$(document).on('tap', function () {
$element.hide();
clearTimeout(timer);
});
$this.on('tap', function (event) {
event.stopPropagation();
});
}
});
/**
* Render swatch controls with options and use tooltips.
* Required two json:
* - jsonConfig (magento's option config)
* - jsonSwatchConfig (swatch's option config)
*
* Tuning:
* - numberToShow (show "more" button if options are more)
* - onlySwatches (hide selectboxes)
* - moreButtonText (text for "more" button)
* - selectorProduct (selector for product container)
* - selectorProductPrice (selector for change price)
*/
$.widget('mage.SwatchRenderer', {
options: {
classes: {
attributeClass: 'swatch-attribute',
attributeLabelClass: 'swatch-attribute-label',
attributeSelectedOptionLabelClass: 'swatch-attribute-selected-option',
attributeOptionsWrapper: 'swatch-attribute-options',
attributeInput: 'swatch-input',
optionClass: 'swatch-option',
selectClass: 'swatch-select',
moreButton: 'swatch-more',
loader: 'swatch-option-loading'
},
// option's json config
jsonConfig: {},
// swatch's json config
jsonSwatchConfig: {},
// selector of parental block of prices and swatches (need to know where to seek for price block)
selectorProduct: '.product-info-main',
// selector of price wrapper (need to know where set price)
selectorProductPrice: '[data-role=priceBox]',
//selector of product images gallery wrapper
mediaGallerySelector: '[data-gallery-role=gallery-placeholder]',
// selector of category product tile wrapper
selectorProductTile: '.product-item',
// number of controls to show (false or zero = show all)
numberToShow: false,
// show only swatch controls
onlySwatches: false,
// enable label for control
enableControlLabel: true,
// control label id
controlLabelId: '',
// text for more button
moreButtonText: $t('More'),
// Callback url for media
mediaCallback: '',
// Local media cache
mediaCache: {},
// Cache for BaseProduct images. Needed when option unset
mediaGalleryInitial: [{}],
// Use ajax to get image data
useAjax: false,
/**
* Defines the mechanism of how images of a gallery should be
* updated when user switches between configurations of a product.
*
* As for now value of this option can be either 'replace' or 'prepend'.
*
* @type {String}
*/
gallerySwitchStrategy: 'replace',
// whether swatches are rendered in product list or on product page
inProductList: false,
// sly-old-price block selector
slyOldPriceSelector: '.sly-old-price',
// tier prise selectors start
tierPriceTemplateSelector: '#tier-prices-template',
tierPriceBlockSelector: '[data-role="tier-price-block"]',
tierPriceTemplate: '',
// tier prise selectors end
// A price label selector
normalPriceLabelSelector: '.product-info-main .normal-price .price-label'
},
/**
* Get chosen product
*
* @returns int|null
*/
getProduct: function () {
var products = this._CalcProducts();
return _.isArray(products) ? products[0] : null;
},
/**
* Get chosen product id
*
* @returns int|null
*/
getProductId: function () {
var products = this._CalcProducts();
return _.isArray(products) && products.length === 1 ? products[0] : null;
},
/**
* @private
*/
_init: function () {
// Don't render the same set of swatches twice
if ($(this.element).attr('data-rendered')) {
return;
}
$(this.element).attr('data-rendered', true);
if (_.isEmpty(this.options.jsonConfig.images)) {
this.options.useAjax = true;
// creates debounced variant of _LoadProductMedia()
// to use it in events handlers instead of _LoadProductMedia()
this._debouncedLoadProductMedia = _.debounce(this._LoadProductMedia.bind(this), 500);
}
this.options.tierPriceTemplate = $(this.options.tierPriceTemplateSelector).html();
if (this.options.jsonConfig !== '' && this.options.jsonSwatchConfig !== '') {
// store unsorted attributes
this.options.jsonConfig.mappedAttributes = _.clone(this.options.jsonConfig.attributes);
this._sortAttributes();
this._RenderControls();
this._setPreSelectedGallery();
$(this.element).trigger('swatch.initialized');
} else {
console.log('SwatchRenderer: No input data received');
}
},
/**
* @private
*/
_sortAttributes: function () {
this.options.jsonConfig.attributes = _.sortBy(this.options.jsonConfig.attributes, function (attribute) {
return parseInt(attribute.position, 10);
});
},
/**
* @private
*/
_create: function () {
var options = this.options,
gallery = $('[data-gallery-role=gallery-placeholder]', '.column.main'),
productData = this._determineProductData(),
$main = productData.isInProductView ?
this.element.parents('.column.main') :
this.element.parents('.product-item-info');
if (productData.isInProductView) {
gallery.data('gallery') ?
this._onGalleryLoaded(gallery) :
gallery.on('gallery:loaded', this._onGalleryLoaded.bind(this, gallery));
} else {
options.mediaGalleryInitial = [{
'img': $main.find('.product-image-photo').attr('src')
}];
}
this.productForm = this.element.parents(this.options.selectorProductTile).find('form:first');
this.inProductList = this.productForm.length > 0;
},
/**
* Determine product id and related data
*
* @returns {{productId: *, isInProductView: bool}}
* @private
*/
_determineProductData: function () {
// Check if product is in a list of products.
var productId,
isInProductView = false;
productId = this.element.parents('.product-item-details')
.find('.price-box.price-final_price').attr('data-product-id');
if (!productId) {
// Check individual product.
productId = $('[name=product]').val();
isInProductView = productId > 0;
}
return {
productId: productId,
isInProductView: isInProductView
};
},
/**
* Render controls
*
* @private
*/
_RenderControls: function () {
var $widget = this,
container = this.element,
classes = this.options.classes,
chooseText = this.options.jsonConfig.chooseText,
showTooltip = this.options.showTooltip;
$widget.optionsMap = {};
$.each(this.options.jsonConfig.attributes, function () {
var item = this,
controlLabelId = 'option-label-' + item.code + '-' + item.id,
options = $widget._RenderSwatchOptions(item, controlLabelId),
select = $widget._RenderSwatchSelect(item, chooseText),
input = $widget._RenderFormInput(item),
listLabel = '',
label = '';
// Show only swatch controls
if ($widget.options.onlySwatches && !$widget.options.jsonSwatchConfig.hasOwnProperty(item.id)) {
return;
}
if ($widget.options.enableControlLabel) {
label +=
'<span id="' + controlLabelId + '" class="' + classes.attributeLabelClass + '">' +
$('<i></i>').text(item.label).html() +
'</span>' +
'<span class="' + classes.attributeSelectedOptionLabelClass + '"></span>';
}
if ($widget.inProductList) {
$widget.productForm.append(input);
input = '';
listLabel = 'aria-label="' + $('<i></i>').text(item.label).html() + '"';
} else {
listLabel = 'aria-labelledby="' + controlLabelId + '"';
}
// Create new control
container.append(
'<div class="' + classes.attributeClass + ' ' + item.code + '" ' +
'data-attribute-code="' + item.code + '" ' +
'data-attribute-id="' + item.id + '">' +
label +
'<div aria-activedescendant="" ' +
'tabindex="0" ' +
'aria-invalid="false" ' +
'aria-required="true" ' +
'role="listbox" ' + listLabel +
'class="' + classes.attributeOptionsWrapper + ' clearfix">' +
options + select +
'</div>' + input +
'</div>'
);
$widget.optionsMap[item.id] = {};
// Aggregate options array to hash (key => value)
$.each(item.options, function () {
if (this.products.length > 0) {
$widget.optionsMap[item.id][this.id] = {
price: parseInt(
$widget.options.jsonConfig.optionPrices[this.products[0]].finalPrice.amount,
10
),
products: this.products
};
}
});
});
if (showTooltip === 1) {
// Connect Tooltip
container
.find('[data-option-type="1"], [data-option-type="2"],' +
' [data-option-type="0"], [data-option-type="3"]')
.SwatchRendererTooltip();
}
// Hide all elements below more button
$('.' + classes.moreButton).nextAll().hide();
// Handle events like click or change
$widget._EventListener();
// Rewind options
$widget._Rewind(container);
//Emulate click on all swatches from Request
$widget._EmulateSelected($.parseQuery());
$widget._EmulateSelected($widget._getSelectedAttributes());
},
/**
* Render swatch options by part of config
*
* @param {Object} config
* @param {String} controlId
* @returns {String}
* @private
*/
_RenderSwatchOptions: function (config, controlId) {
var optionConfig = this.options.jsonSwatchConfig[config.id],
optionClass = this.options.classes.optionClass,
sizeConfig = this.options.jsonSwatchImageSizeConfig,
moreLimit = parseInt(this.options.numberToShow, 10),
moreClass = this.options.classes.moreButton,
moreText = this.options.moreButtonText,
countAttributes = 0,
html = '';
if (!this.options.jsonSwatchConfig.hasOwnProperty(config.id)) {
return '';
}
$.each(config.options, function (index) {
var id,
type,
value,
thumb,
label,
width,
height,
attr,
swatchImageWidth,
swatchImageHeight;
if (!optionConfig.hasOwnProperty(this.id)) {
return '';
}
// Add more button
if (moreLimit === countAttributes++) {
html += '<a href="#" class="' + moreClass + '"><span>' + moreText + '</span></a>';
}
id = this.id;
type = parseInt(optionConfig[id].type, 10);
value = optionConfig[id].hasOwnProperty('value') ?
$('<i></i>').text(optionConfig[id].value).html() : '';
thumb = optionConfig[id].hasOwnProperty('thumb') ? optionConfig[id].thumb : '';
width = _.has(sizeConfig, 'swatchThumb') ? sizeConfig.swatchThumb.width : 110;
height = _.has(sizeConfig, 'swatchThumb') ? sizeConfig.swatchThumb.height : 90;
label = this.label ? $('<i></i>').text(this.label).html() : '';
attr =
' id="' + controlId + '-item-' + id + '"' +
' index="' + index + '"' +
' aria-checked="false"' +
' aria-describedby="' + controlId + '"' +
' tabindex="0"' +
' data-option-type="' + type + '"' +
' data-option-id="' + id + '"' +
' data-option-label="' + label + '"' +
' aria-label="' + label + '"' +
' role="option"' +
' data-thumb-width="' + width + '"' +
' data-thumb-height="' + height + '"';
attr += thumb !== '' ? ' data-option-tooltip-thumb="' + thumb + '"' : '';
attr += value !== '' ? ' data-option-tooltip-value="' + value + '"' : '';
swatchImageWidth = _.has(sizeConfig, 'swatchImage') ? sizeConfig.swatchImage.width : 30;
swatchImageHeight = _.has(sizeConfig, 'swatchImage') ? sizeConfig.swatchImage.height : 20;
if (!this.hasOwnProperty('products') || this.products.length <= 0) {
attr += ' data-option-empty="true"';
}
if (type === 0) {
// Text
html += '<div class="' + optionClass + ' text" ' + attr + '>' + (value ? value : label) +
'</div>';
} else if (type === 1) {
// Color
html += '<div class="' + optionClass + ' color" ' + attr +
' style="background: ' + value +
' no-repeat center; background-size: cover;">' + '' +
'</div>';
} else if (type === 2) {
// Image
html += '<div class="' + optionClass + ' image" ' + attr +
' style="background: url(' + value + ') no-repeat center; background-size: cover;width:' +
swatchImageWidth + 'px; height:' + swatchImageHeight + 'px">' + '' +
'</div>';
} else if (type === 3) {
// Clear
html += '<div class="' + optionClass + '" ' + attr + '></div>';
} else {
// Default
html += '<div class="' + optionClass + '" ' + attr + '>' + label + '</div>';
}
});
return html;
},
/**
* Render select by part of config
*
* @param {Object} config
* @param {String} chooseText
* @returns {String}
* @private
*/
_RenderSwatchSelect: function (config, chooseText) {
var html;
if (this.options.jsonSwatchConfig.hasOwnProperty(config.id)) {
return '';
}
html =
'<select class="' + this.options.classes.selectClass + ' ' + config.code + '">' +
'<option value="0" data-option-id="0">' + chooseText + '</option>';
$.each(config.options, function () {
var label = this.label,
attr = ' value="' + this.id + '" data-option-id="' + this.id + '"';
if (!this.hasOwnProperty('products') || this.products.length <= 0) {
attr += ' data-option-empty="true"';
}
html += '<option ' + attr + '>' + label + '</option>';
});
html += '</select>';
return html;
},
/**
* Input for submit form.
* This control shouldn't have "type=hidden", "display: none" for validation work :(
*
* @param {Object} config
* @private
*/
_RenderFormInput: function (config) {
return '<input class="' + this.options.classes.attributeInput + ' super-attribute-select" ' +
'name="super_attribute[' + config.id + ']" ' +
'type="text" ' +
'value="" ' +
'data-selector="super_attribute[' + config.id + ']" ' +
'data-validate="{required: true}" ' +
'aria-required="true" ' +
'aria-invalid="false">';
},
/**
* Event listener
*
* @private
*/
_EventListener: function () {
var $widget = this,
options = this.options.classes,
target;
$widget.element.on('click', '.' + options.optionClass, function () {
return $widget._OnClick($(this), $widget);
});
$widget.element.on('change', '.' + options.selectClass, function () {
return $widget._OnChange($(this), $widget);
});
$widget.element.on('click', '.' + options.moreButton, function (e) {
e.preventDefault();
return $widget._OnMoreClick($(this));
});
$widget.element.on('keydown', function (e) {
if (e.which === 13) {
target = $(e.target);
if (target.is('.' + options.optionClass)) {
return $widget._OnClick(target, $widget);
} else if (target.is('.' + options.selectClass)) {
return $widget._OnChange(target, $widget);
} else if (target.is('.' + options.moreButton)) {
e.preventDefault();
return $widget._OnMoreClick(target);
}
}
});
},
/**
* Load media gallery using ajax or json config.
*
* @private
*/
_loadMedia: function () {
var $main = this.inProductList ?
this.element.parents('.product-item-info') :
this.element.parents('.column.main'),
images;
if (this.options.useAjax) {
this._debouncedLoadProductMedia();
} else {
images = this.options.jsonConfig.images[this.getProduct()];
if (!images) {
images = this.options.mediaGalleryInitial;
}
var main_img,thumb_img;
$.each(images[0], function(index, name_value){
if (index == "thumb"){
thumb_img = name_value;
}
if(index == "img"){
main_img = name_value;
}
});
$(".main-image").attr("src",main_img);
$(".main-image-wrapper > picture > source:first ").attr("srcset",main_img);
$(".main-image-wrapper > picture > source:last ").attr("srcset",main_img);
//移除之前选中图
$(".color_img").remove();
$(".thumbnails > a").removeClass("active");
$(".thumbnails >a:first").before("" +
"<a class=\"item active color_img \" href=\"#\" name=\""+ main_img +"\" title=\"Image\">\n" +
" <picture>\n" +
" <source type=\"image/webp\" srcset=\""+thumb_img+"\">\n" +
" <source type=\"image/jpg\" srcset=\""+thumb_img+"\">\n" +
" <img loading=\"lazy\" alt=\"Image\" src=\""+thumb_img+"\" width=\"50\" height=\"66\"> </picture>\n" +
"\n" +
"\n" +
" </a>");
$(".mobile-pic >a:first").before("" +
"<a class=\"item color_img \" href=\"#\" name=\""+ main_img +"\" title=\"Image\" data-caption=\"bear\" data-id=\"bear\" data-group=\"animal\" >\n" +
" <picture>\n" +
" <source type=\"image/webp\" srcset=\""+main_img+"\">\n" +
" <source type=\"image/jpg\" srcset=\""+main_img+"\">\n" +
" <img loading=\"lazy\" alt=\"Image\" src=\""+main_img+"\" > </picture>\n" +
"\n" +
"\n" +
" </a>");
this.updateBaseImage(this._sortImages(images), $main, !this.inProductList);
}
},
/**
* Sorting images array
*
* @private
*/
_sortImages: function (images) {
return _.sortBy(images, function (image) {
return parseInt(image.position, 10);
});
},
/**
* Event for swatch options
*
* @param {Object} $this
* @param {Object} $widget
* @private
*/
_OnClick: function ($this, $widget) {
var $parent = $this.parents('.' + $widget.options.classes.attributeClass),
$wrapper = $this.parents('.' + $widget.options.classes.attributeOptionsWrapper),
$label = $parent.find('.' + $widget.options.classes.attributeSelectedOptionLabelClass),
attributeId = $parent.data('attribute-id'),
$input = $parent.find('.' + $widget.options.classes.attributeInput),
checkAdditionalData = JSON.parse(this.options.jsonSwatchConfig[attributeId]['additional_data']),
$priceBox = $widget.element.parents($widget.options.selectorProduct)
.find(this.options.selectorProductPrice);
if ($widget.inProductList) {
$input = $widget.productForm.find(
'.' + $widget.options.classes.attributeInput + '[name="super_attribute[' + attributeId + ']"]'
);
}
if ($this.hasClass('disabled')) {
return;
}
if ($this.hasClass('selected')) {
$parent.removeAttr('data-option-selected').find('.selected').removeClass('selected');
$input.val('');
$label.text('');
$this.attr('aria-checked', false);
} else {
$parent.attr('data-option-selected', $this.data('option-id')).find('.selected').removeClass('selected');
$label.text($this.data('option-label'));
$input.val($this.data('option-id'));
$input.attr('data-attr-name', this._getAttributeCodeById(attributeId));
$this.addClass('selected');
$widget._toggleCheckedAttributes($this, $wrapper);
}
$widget._Rebuild();
if ($priceBox.is(':data(mage-priceBox)')) {
$widget._UpdatePrice();
}
$(document).trigger('updateMsrpPriceBlock',
[
this._getSelectedOptionPriceIndex(),
$widget.options.jsonConfig.optionPrices,
$priceBox
]);
if (parseInt(checkAdditionalData['update_product_preview_image'], 10) === 1) {
$widget._loadMedia();
}
$input.trigger('change');
},
/**
* Get selected option price index
*
* @return {String|undefined}
* @private
*/
_getSelectedOptionPriceIndex: function () {
var allowedProduct = this._getAllowedProductWithMinPrice(this._CalcProducts());
if (_.isEmpty(allowedProduct)) {
return undefined;
}
return allowedProduct;
},
/**
* Get human readable attribute code (eg. size, color) by it ID from configuration
*
* @param {Number} attributeId
* @returns {*}
* @private
*/
_getAttributeCodeById: function (attributeId) {
var attribute = this.options.jsonConfig.mappedAttributes[attributeId];
return attribute ? attribute.code : attributeId;
},
/**
* Toggle accessibility attributes
*
* @param {Object} $this
* @param {Object} $wrapper
* @private
*/
_toggleCheckedAttributes: function ($this, $wrapper) {
$wrapper.attr('aria-activedescendant', $this.attr('id'))
.find('.' + this.options.classes.optionClass).attr('aria-checked', false);
$this.attr('aria-checked', true);
},
/**
* Event for select
*
* @param {Object} $this
* @param {Object} $widget
* @private
*/
_OnChange: function ($this, $widget) {
var $parent = $this.parents('.' + $widget.options.classes.attributeClass),
attributeId = $parent.data('attribute-id'),
$input = $parent.find('.' + $widget.options.classes.attributeInput);
if ($widget.productForm.length > 0) {
$input = $widget.productForm.find(
'.' + $widget.options.classes.attributeInput + '[name="super_attribute[' + attributeId + ']"]'
);
}
if ($this.val() > 0) {
$parent.attr('data-option-selected', $this.val());
$input.val($this.val());
} else {
$parent.removeAttr('data-option-selected');
$input.val('');
}
$widget._Rebuild();
$widget._UpdatePrice();
$widget._loadMedia();
$input.trigger('change');
},
/**
* Event for more switcher
*
* @param {Object} $this
* @private
*/
_OnMoreClick: function ($this) {
$this.nextAll().show();
$this.blur().remove();
},
/**
* Rewind options for controls
*
* @private
*/
_Rewind: function (controls) {
controls.find('div[data-option-id], option[data-option-id]').removeClass('disabled').removeAttr('disabled');
controls.find('div[data-option-empty], option[data-option-empty]')
.attr('disabled', true)
.addClass('disabled')
.attr('tabindex', '-1');
},
/**
* Rebuild container
*
* @private
*/
_Rebuild: function () {
var $widget = this,
controls = $widget.element.find('.' + $widget.options.classes.attributeClass + '[data-attribute-id]'),
selected = controls.filter('[data-option-selected]');
// Enable all options
$widget._Rewind(controls);
// done if nothing selected
if (selected.length <= 0) {
return;
}
// Disable not available options
controls.each(function () {
var $this = $(this),
id = $this.data('attribute-id'),
products = $widget._CalcProducts(id);
if (selected.length === 1 && selected.first().data('attribute-id') === id) {
return;
}
$this.find('[data-option-id]').each(function () {
var $element = $(this),
option = $element.data('option-id');
if (!$widget.optionsMap.hasOwnProperty(id) || !$widget.optionsMap[id].hasOwnProperty(option) ||
$element.hasClass('selected') ||
$element.is(':selected')) {
return;
}
if (_.intersection(products, $widget.optionsMap[id][option].products).length <= 0) {
$element.attr('disabled', true).addClass('disabled');
}
});
});
},
/**
* Get selected product list
*
* @returns {Array}
* @private
*/
_CalcProducts: function ($skipAttributeId) {
var $widget = this,
selectedOptions = '.' + $widget.options.classes.attributeClass + '[data-option-selected]',
products = [];
// Generate intersection of products
$widget.element.find(selectedOptions).each(function () {
var id = $(this).data('attribute-id'),
option = $(this).attr('data-option-selected');
if ($skipAttributeId !== undefined && $skipAttributeId === id) {
return;
}
if (!$widget.optionsMap.hasOwnProperty(id) || !$widget.optionsMap[id].hasOwnProperty(option)) {
return;
}
if (products.length === 0) {
products = $widget.optionsMap[id][option].products;
} else {
products = _.intersection(products, $widget.optionsMap[id][option].products);
}
});
return products;
},
/**
* Update total price
*
* @private
*/
_UpdatePrice: function () {
var $widget = this,
$product = $widget.element.parents($widget.options.selectorProduct),
$productPrice = $product.find(this.options.selectorProductPrice),
result = $widget._getNewPrices(),
tierPriceHtml,
isShow;
$productPrice.trigger(
'updatePrice',
{
'prices': $widget._getPrices(result, $productPrice.priceBox('option').prices)
}
);
isShow = typeof result != 'undefined' && result.oldPrice.amount !== result.finalPrice.amount;
$productPrice.find('span:first').toggleClass('special-price', isShow);
$product.find(this.options.slyOldPriceSelector)[isShow ? 'show' : 'hide']();
if (typeof result != 'undefined' && result.tierPrices && result.tierPrices.length) {
if (this.options.tierPriceTemplate) {
tierPriceHtml = mageTemplate(
this.options.tierPriceTemplate,
{
'tierPrices': result.tierPrices,
'$t': $t,
'currencyFormat': this.options.jsonConfig.currencyFormat,
'priceUtils': priceUtils
}
);
$(this.options.tierPriceBlockSelector).html(tierPriceHtml).show();
}
} else {
$(this.options.tierPriceBlockSelector).hide();
}
$(this.options.normalPriceLabelSelector).hide();
_.each($('.' + this.options.classes.attributeOptionsWrapper), function (attribute) {
if ($(attribute).find('.' + this.options.classes.optionClass + '.selected').length === 0) {
if ($(attribute).find('.' + this.options.classes.selectClass).length > 0) {
_.each($(attribute).find('.' + this.options.classes.selectClass), function (dropdown) {
if ($(dropdown).val() === '0') {
$(this.options.normalPriceLabelSelector).show();
}
}.bind(this));
} else {
$(this.options.normalPriceLabelSelector).show();
}
}
}.bind(this));
},
/**
* Get new prices for selected options
*
* @returns {*}
* @private
*/
_getNewPrices: function () {
var $widget = this,
newPrices = $widget.options.jsonConfig.prices,
allowedProduct = this._getAllowedProductWithMinPrice(this._CalcProducts());
if (!_.isEmpty(allowedProduct)) {
newPrices = this.options.jsonConfig.optionPrices[allowedProduct];
}
return newPrices;
},
/**
* Get prices
*
* @param {Object} newPrices
* @param {Object} displayPrices
* @returns {*}
* @private
*/
_getPrices: function (newPrices, displayPrices) {
var $widget = this;
if (_.isEmpty(newPrices)) {
newPrices = $widget._getNewPrices();
}
_.each(displayPrices, function (price, code) {
if (newPrices[code]) {
displayPrices[code].amount = newPrices[code].amount - displayPrices[code].amount;
}
});
return displayPrices;
},
/**
* Get product with minimum price from selected options.
*
* @param {Array} allowedProducts
* @returns {String}
* @private
*/
_getAllowedProductWithMinPrice: function (allowedProducts) {
var optionPrices = this.options.jsonConfig.optionPrices,
product = {},
optionFinalPrice, optionMinPrice;
_.each(allowedProducts, function (allowedProduct) {
optionFinalPrice = parseFloat(optionPrices[allowedProduct].finalPrice.amount);
if (_.isEmpty(product) || optionFinalPrice < optionMinPrice) {
optionMinPrice = optionFinalPrice;
product = allowedProduct;
}
}, this);
return product;
},
/**
* Gets all product media and change current to the needed one
*
* @private
*/
_LoadProductMedia: function () {
var $widget = this,
$this = $widget.element,
productData = this._determineProductData(),
mediaCallData,
mediaCacheKey,
/**
* Processes product media data
*
* @param {Object} data
* @returns void
*/
mediaSuccessCallback = function (data) {
if (!(mediaCacheKey in $widget.options.mediaCache)) {
$widget.options.mediaCache[mediaCacheKey] = data;
}
$widget._ProductMediaCallback($this, data, productData.isInProductView);
setTimeout(function () {
$widget._DisableProductMediaLoader($this);
}, 300);
};
if (!$widget.options.mediaCallback) {
return;
}
mediaCallData = {
'product_id': this.getProduct()
};
mediaCacheKey = JSON.stringify(mediaCallData);
if (mediaCacheKey in $widget.options.mediaCache) {
$widget._XhrKiller();
$widget._EnableProductMediaLoader($this);
mediaSuccessCallback($widget.options.mediaCache[mediaCacheKey]);
} else {
mediaCallData.isAjax = true;
$widget._XhrKiller();
$widget._EnableProductMediaLoader($this);
$widget.xhr = $.ajax({
url: $widget.options.mediaCallback,
cache: true,
type: 'GET',
dataType: 'json',
data: mediaCallData,
success: mediaSuccessCallback
}).done(function () {
$widget._XhrKiller();
});
}
},
/**
* Enable loader
*
* @param {Object} $this
* @private
*/
_EnableProductMediaLoader: function ($this) {
var $widget = this;
if ($('body.catalog-product-view').length > 0) {
$this.parents('.column.main').find('.photo.image')
.addClass($widget.options.classes.loader);
} else {
//Category View
$this.parents('.product-item-info').find('.product-image-photo')
.addClass($widget.options.classes.loader);
}
},
/**
* Disable loader
*
* @param {Object} $this
* @private
*/
_DisableProductMediaLoader: function ($this) {
var $widget = this;
if ($('body.catalog-product-view').length > 0) {
$this.parents('.column.main').find('.photo.image')
.removeClass($widget.options.classes.loader);
} else {
//Category View
$this.parents('.product-item-info').find('.product-image-photo')
.removeClass($widget.options.classes.loader);
}
},
/**
* Callback for product media
*
* @param {Object} $this
* @param {String} response
* @param {Boolean} isInProductView
* @private
*/
_ProductMediaCallback: function ($this, response, isInProductView) {
var $main = isInProductView ? $this.parents('.column.main') : $this.parents('.product-item-info'),
$widget = this,
images = [],
/**
* Check whether object supported or not
*
* @param {Object} e
* @returns {*|Boolean}
*/
support = function (e) {
return e.hasOwnProperty('large') && e.hasOwnProperty('medium') && e.hasOwnProperty('small');
};
if (_.size($widget) < 1 || !support(response)) {
this.updateBaseImage(this.options.mediaGalleryInitial, $main, isInProductView);
return;
}
images.push({
full: response.large,
img: response.medium,
thumb: response.small,
isMain: true
});
if (response.hasOwnProperty('gallery')) {
$.each(response.gallery, function () {
if (!support(this) || response.large === this.large) {
return;
}
images.push({
full: this.large,
img: this.medium,
thumb: this.small
});
});
}
this.updateBaseImage(images, $main, isInProductView);
},
/**
* Check if images to update are initial and set their type
* @param {Array} images
*/
_setImageType: function (images) {
var initial = this.options.mediaGalleryInitial[0].img;
if (images[0].img === initial) {
images = $.extend(true, [], this.options.mediaGalleryInitial);
} else {
images.map(function (img) {
if (!img.type) {
img.type = 'image';
}
});
}
return images;
},
/**
* Update [gallery-placeholder] or [product-image-photo]
* @param {Array} images
* @param {jQuery} context
* @param {Boolean} isInProductView
*/
updateBaseImage: function (images, context, isInProductView) {
var justAnImage = images[0],
initialImages = this.options.mediaGalleryInitial,
imagesToUpdate,
gallery = context.find(this.options.mediaGallerySelector).data('gallery'),
isInitial;
if (isInProductView) {
if (_.isUndefined(gallery)) {
context.find(this.options.mediaGallerySelector).on('gallery:loaded', function () {
this.updateBaseImage(images, context, isInProductView);
}.bind(this));
return;
}
imagesToUpdate = images.length ? this._setImageType($.extend(true, [], images)) : [];
isInitial = _.isEqual(imagesToUpdate, initialImages);
if (this.options.gallerySwitchStrategy === 'prepend' && !isInitial) {
imagesToUpdate = imagesToUpdate.concat(initialImages);
}
imagesToUpdate = this._setImageIndex(imagesToUpdate);
gallery.updateData(imagesToUpdate);
this._addFotoramaVideoEvents(isInitial);
} else if (justAnImage && justAnImage.img) {
context.find('.product-image-photo').attr('src', justAnImage.img);
}
},
/**
* Add video events
*
* @param {Boolean} isInitial
* @private
*/
_addFotoramaVideoEvents: function (isInitial) {
if (_.isUndefined($.mage.AddFotoramaVideoEvents)) {
return;
}
if (isInitial) {
$(this.options.mediaGallerySelector).AddFotoramaVideoEvents();
return;
}
$(this.options.mediaGallerySelector).AddFotoramaVideoEvents({
selectedOption: this.getProduct(),
dataMergeStrategy: this.options.gallerySwitchStrategy
});
},
/**
* Set correct indexes for image set.
*
* @param {Array} images
* @private
*/
_setImageIndex: function (images) {
var length = images.length,
i;
for (i = 0; length > i; i++) {
images[i].i = i + 1;
}
return images;
},
/**
* Kill doubled AJAX requests
*
* @private
*/
_XhrKiller: function () {
var $widget = this;
if ($widget.xhr !== undefined && $widget.xhr !== null) {
$widget.xhr.abort();
$widget.xhr = null;
}
},
/**
* Emulate mouse click on all swatches that should be selected
* @param {Object} [selectedAttributes]
* @private
*/
_EmulateSelected: function (selectedAttributes) {
$.each(selectedAttributes, $.proxy(function (attributeCode, optionId) {
var elem = this.element.find('.' + this.options.classes.attributeClass +
'[data-attribute-code="' + attributeCode + '"] [data-option-id="' + optionId + '"]'),
parentInput = elem.parent();
if (elem.hasClass('selected')) {
return;
}
if (parentInput.hasClass(this.options.classes.selectClass)) {
parentInput.val(optionId);
parentInput.trigger('change');
} else {
elem.trigger('click');
}
}, this));
},
/**
* Emulate mouse click or selection change on all swatches that should be selected
* @param {Object} [selectedAttributes]
* @private
*/
_EmulateSelectedByAttributeId: function (selectedAttributes) {
$.each(selectedAttributes, $.proxy(function (attributeId, optionId) {
var elem = this.element.find('.' + this.options.classes.attributeClass +
'[data-attribute-id="' + attributeId + '"] [data-option-id="' + optionId + '"]'),
parentInput = elem.parent();
if (elem.hasClass('selected')) {
return;
}
if (parentInput.hasClass(this.options.classes.selectClass)) {
parentInput.val(optionId);
parentInput.trigger('change');
} else {
elem.trigger('click');
}
}, this));
},
/**
* Get default options values settings with either URL query parameters
* @private
*/
_getSelectedAttributes: function () {
var hashIndex = window.location.href.indexOf('#'),
selectedAttributes = {},
params;
if (hashIndex !== -1) {
params = $.parseQuery(window.location.href.substr(hashIndex + 1));
selectedAttributes = _.invert(_.mapObject(_.invert(params), function (attributeId) {
var attribute = this.options.jsonConfig.mappedAttributes[attributeId];
return attribute ? attribute.code : attributeId;
}.bind(this)));
}
return selectedAttributes;
},
/**
* Callback which fired after gallery gets initialized.
*
* @param {HTMLElement} element - DOM element associated with a gallery.
*/
_onGalleryLoaded: function (element) {
var galleryObject = element.data('gallery');
this.options.mediaGalleryInitial = galleryObject.returnCurrentImages();
},
/**
* Sets mediaCache for cases when jsonConfig contains preSelectedGallery on layered navigation result pages
*
* @private
*/
_setPreSelectedGallery: function () {
var mediaCallData;
if (this.options.jsonConfig.preSelectedGallery) {
mediaCallData = {
'product_id': this.getProduct()
};
this.options.mediaCache[JSON.stringify(mediaCallData)] = this.options.jsonConfig.preSelectedGallery;
}
}
});
return $.mage.SwatchRenderer;
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment