2

well we have heavily customized Magento 2 website. The swatch customizing is done in:

app\code\My_Package\CustomSwatches\view\frontend\web\js\SwatchRenderer.js

by overriding:

vendor\magento\module-swatches\view\frontend\web\js\SwatchRenderer.js

This is the edited SwatchRenderer.js portion:

  $.widget('custom.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'
  },
  jsonConfig: {}, // option's json config
  jsonSwatchConfig: {}, // swatch's json config
  selectorProduct: '.product-info-main', // selector of parental block of prices and swatches (need to know where to seek for price block)
  selectorProductPrice: '[data-role=priceBox]', // selector of price wrapper (need to know where set price)
  numberToShow: false, // number of controls to show (false or zero = show all)
  onlySwatches: false, // show only swatch controls
  enableControlLabel: true, // enable label for control
  moreButtonText: 'More', // text for more button
  mediaCallback: '', // Callback url for media
  mediaGalleryInitial: [{}] // Cache for BaseProduct images. Needed when option unset
},

/**
 * Get chosen product
 *
 * @returns array
 */
getProduct: function() {
  return this._CalcProducts().shift();
},

/**
 * @private
 */
_init: function() {
  if (this.options.jsonConfig != '' && this.options.jsonSwatchConfig != '') {
    this._RenderControls();
  } else {
    console.log('SwatchRenderer: No input data received');
  }
},

/**
 * @private
 */
_create: function() {
  var options = this.options,
    gallery = $('[data-gallery-role=gallery-placeholder]', '.column.main'),
    isProductViewExist = $('body.catalog-product-view').size() > 0,
    $main = isProductViewExist ?
    this.element.parents('.column.main') :
    this.element.parents('.product-item-info');

  if (isProductViewExist) {
    gallery.on('gallery:loaded', function() {
      var galleryObject = gallery.data('gallery');

      options.mediaGalleryInitial = galleryObject.returnCurrentImages();
    });
  } else {
    options.mediaGalleryInitial = [{
      'img': $main.find('.product-image-photo').attr('src')
    }];
  }
},

/**
 * Render controls
 *
 * @private
 */
_RenderControls: function() {
  var $widget = this,
    container = this.element,
    classes = this.options.classes,
    chooseText = this.options.jsonConfig.chooseText;

  $widget.optionsMap = {};

  $.each(this.options.jsonConfig.attributes, function() {
    var item = this,
      options = $widget._RenderSwatchOptions(item),
      select = $widget._RenderSwatchSelect(item, chooseText),
      input = $widget._RenderFormInput(item),
      fake_tabs = $widget._RenderFakeTabs(item),
      label = '';

    // Show only swatch controls
    if ($widget.options.onlySwatches && !$widget.options.jsonSwatchConfig.hasOwnProperty(item.id)) {
      return;
    }

    if ($widget.options.enableControlLabel) {
      if (item.code == "admire_gender") {
        label += '';
      } else {
        label +=
          '<span class="' + classes.attributeLabelClass + '">' + item.label + '</span>' +
          '<span class="' + classes.attributeSelectedOptionLabelClass + '"></span>';
      }
    }

    // Create new control
    if (item.code == "admire_gender") {
      container.append('<div class="' + classes.attributeClass + ' ' + item.code + '" attribute-code="' + item.code + '" attribute-id="' + item.id + '">' + label +
        '<div class="' + classes.attributeOptionsWrapper + ' clearfix gender-wrapper"><ul class="nav nav-tabs" role="tablist">' + options + select + '</ul>' +
        '<div class="tab-content">' + fake_tabs + '</div></div>' + input + '</div>');
    } else if (item.code == "admire_color") {
      container.append(
        '<div class="' + classes.attributeClass + ' ' + item.code + '" attribute-code="' + item.code + '" attribute-id="' + item.id + '">' + label +
        '<div class="' + classes.attributeOptionsWrapper + ' clearfix">' + options + select + '<a href="#" class="colors-explained">Colors explained</a></div>' + input + '</div>'
      );
    } else if (item.code == "admire_size") {
      container.append(
        '<div class="' + classes.attributeClass + ' ' + item.code + '" attribute-code="' + item.code + '" attribute-id="' + item.id + '">' + label +
        '<div class="' + classes.attributeOptionsWrapper + ' clearfix">' + options + select + '<a href="#" class="size-chart">SIZE CHARTS</a></div>' + input + '</div>'
      );
    } else {
      container.append(
        '<div class="' + classes.attributeClass + ' ' + item.code + '" attribute-code="' + item.code + '" attribute-id="' + item.id + '">' + label +
        '<div 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
        };
      }
    });
  });

  // Connect Tooltip
  container
    .find('[option-type="1"], [option-type="2"], [option-type="0"], [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();
},

/**
 * Render swatch options by part of config
 *
 * @param config
 * @returns {string}
 * @private
 */
_RenderSwatchOptions: function(config) {
  if (!this.options.jsonSwatchConfig.hasOwnProperty(config.id)) {
    return '';
  }

  var optionConfig = this.options.jsonSwatchConfig[config.id],
    optionClass = this.options.classes.optionClass,
    moreLimit = this.options.numberToShow,
    moreClass = this.options.classes.moreButton,
    moreText = this.options.moreButtonText,
    countAttributes = 0,
    html = '';
  var flag = 0;
  var activeClass = '';


  $.each(config.options, function() {
    if (!optionConfig.hasOwnProperty(this.id)) {
      return '';
    }

    // Add more button
    if (moreLimit != false && moreLimit == countAttributes++) {
      html += '<a href="#" class="' + moreClass + '">' + moreText + '</a>';
    }

    var id = this.id,
      type = optionConfig[id].type,
      value = optionConfig[id].hasOwnProperty('value') ? optionConfig[id].value : '',
      thumb = optionConfig[id].hasOwnProperty('thumb') ? optionConfig[id].thumb : '',
      label = this.label ? this.label : '',
      attr =
      ' option-type="' + type + '"' +
      ' option-id="' + id + '"' +
      ' option-label="' + label + '"' +
      ' option-tooltip-thumb="' + thumb + '"' +
      ' option-tooltip-value="' + value + '"';

    if (!this.hasOwnProperty('products') || this.products.length <= 0) {
      attr += ' option-empty="true"';
    }

    // Text
    if (type == 0) {
      if (config.code == "admire_gender") {
        ++flag;
        if (flag == 1) {
          activeClass = 'active';
        } else {
          activeClass = '';
        }
        html += '<li class="' + optionClass + ' ' + activeClass + ' text" ' + attr + ' role="presentation"><a href="#' + value + '" area-controls="' + value + '" role="tab" data-toggle="tab">' + (value ? value : label) + '</a></li>';
      } else {
        html += '<div class="' + optionClass + ' text" ' + attr + '>' + (value ? value : label) + '</div>';
      }
    }


    // Color
    else if (type == 1) {
      html += '<div class="' + optionClass + ' color" ' + attr +
        '" style="background: ' + thumb + ' no-repeat center; background-size: initial;">' + '' + '</div>';
    }

    // Image
    else if (type == 2) {
      html += '<div class="' + optionClass + ' image" ' + attr +
        '" style="background: url(' + thumb + ') no-repeat center; background-size: initial;">' + '' + '</div>';
    }

    // Clear
    else if (type == 3) {
      html += '<div class="' + optionClass + '" ' + attr + '></div>';
    }

    // Default
    else {
      html += '<div class="' + optionClass + '" ' + attr + '>' + label + '</div>';
    }
  });

  return html;
},

/**
 * Render select by part of config
 *
 * @param config
 * @param chooseText
 * @returns {string}
 * @private
 */
_RenderSwatchSelect: function(config, chooseText) {
  if (this.options.jsonSwatchConfig.hasOwnProperty(config.id)) {
    return '';
  }

  var html =
    '<select class="' + this.options.classes.selectClass + ' ' + config.code + '">' +
    '<option value="0" option-id="0">' + chooseText + '</option>';

  $.each(config.options, function() {
    var label = this.label,
      attr = ' value="' + this.id + '" option-id="' + this.id + '"';

    if (!this.hasOwnProperty('products') || this.products.length <= 0) {
      attr += ' 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 config
 * @private
 */
_RenderFormInput: function(config) {
  return '<input class="' + this.options.classes.attributeInput + '" ' +
    'name="super_attribute[' + config.id + ']" ' +
    'value="" ' +
    'data-validate="{required:true}" ' +
    'aria-required="true" ' +
    'aria-invalid="true" ' +
    'style="visibility: hidden; position:absolute; left:-1000px">';
},

/**
 * Input for fake tabs.
 * This control shouldn't have "type=hidden", "display: none" for validation work :(
 *
 * @param config
 * @private
 */
_RenderFakeTabs: function(config) {
  var tabs = '';
  var configOptions = this.options.jsonSwatchConfig[config.id];
  var flag = 0;
  var tab_image = '';
  var activeClass = '';
  $.each(config.options, function() {
    var id = this.id;
    var value = configOptions[id].hasOwnProperty('value') ? configOptions[id].value : '';
    if (config.code == 'admire_gender') {
      ++flag;
      if (flag == 1) {
        activeClass = 'active in';
      } else {
        activeClass = '';
      }
      if (value == "Men") {
        tab_image = gender_image_MC;
      } else if (value == "Women") {
        tab_image = gender_image_W;
      } else if (value = "Children") {
        tab_image = gender_image_MC;
      }
      tabs += '<div role="tabpanel" class="tab-pane fade ' + activeClass + '" id="' + value + '"><a href="#"><img src="' + tab_image + '" alt=""/></a></div>';
    }
  });
  return tabs;
},

/**
 * Event listener
 *
 * @private
 */
_EventListener: function() {

  var $widget = this;

  $widget.element.on('click', '.' + this.options.classes.optionClass, function() {
    return $widget._OnClick($(this), $widget);
  });

  $widget.element.on('change', '.' + this.options.classes.selectClass, function() {
    return $widget._OnChange($(this), $widget);
  });

  $widget.element.on('click', '.' + this.options.classes.moreButton, function(e) {
    e.preventDefault();
    return $widget._OnMoreClick($(this));
  });
},

/**
 * Event for swatch options
 *
 * @param $this
 * @param $widget
 * @private
 */
_OnClick: function($this, $widget) {


  var muaw_color, admire_gender, muaw_size = '';

  if ($this.parent().parent().hasClass('admire_color')) {
    muaw_color = $this.attr('option-label');
    $('.product-selection-color').text(muaw_color + ' t-shirt, ');
  } else if ($this.parent().parent().hasClass('admire_size')) {
    muaw_size = $this.attr('option-label');
    $('.product-selection-size').text('size ' + muaw_size);
  } else if ($this.parent().parent().parent().hasClass('admire_gender')) {
    if ($this.attr('option-label') == 'Men') {
      admire_gender = 'short sleeve'
    } else if ($this.attr('option-label') == 'Women') {
      admire_gender = 'long sleeve'
    } else if ($this.attr('option-label') == 'Children') {
      admire_gender = 'short sleeve'
    }
    $('.product-selection-gender').text(admire_gender + ', ');
  }


  var $parent = $this.parents('.' + $widget.options.classes.attributeClass),
    $label = $parent.find('.' + $widget.options.classes.attributeSelectedOptionLabelClass),
    $input = $parent.find('.' + $widget.options.classes.attributeInput);

  if ($this.hasClass('disabled')) {
    return;
  }

  if ($this.hasClass('selected')) {
    $parent.removeAttr('option-selected').find('.selected').removeClass('selected');
    $input.val('');
    $label.text('');
  } else {
    $parent.attr('option-selected', $this.attr('option-id')).find('.selected').removeClass('selected');
    $label.text($this.attr('option-label'));
    $input.val($this.attr('option-id'));
    $this.addClass('selected');
  }

  $widget._Rebuild();

  if ($widget.element.parents($widget.options.selectorProduct)
    .find(this.options.selectorProductPrice).is(':data(mage-priceBox)')
  ) {
    $widget._UpdatePrice();
  }

  $widget._LoadProductMedia();

  $('.product-page-left .product-image-design').css('display', 'block');
},

/**
 * Event for select
 *
 * @param $this
 * @param $widget
 * @private
 */
_OnChange: function($this, $widget) {
  var $parent = $this.parents('.' + $widget.options.classes.attributeClass),
    $input = $parent.find('.' + $widget.options.classes.attributeInput);

  if ($this.val() > 0) {
    $parent.attr('option-selected', $this.val());
    $input.val($this.val());
  } else {
    $parent.removeAttr('option-selected');
    $input.val('');
  }

  $widget._Rebuild();
  $widget._UpdatePrice();
  $widget._LoadProductMedia();
},

/**
 * Event for more switcher
 *
 * @param $this
 * @private
 */
_OnMoreClick: function($this) {
  $this.nextAll().show();
  $this.blur().remove();
},

/**
 * Rewind options for controls
 *
 * @private
 */
_Rewind: function(controls) {
  controls.find('div[option-id], option[option-id]').removeClass('disabled').removeAttr('disabled');
  controls.find('li[option-id], option[option-id]').removeClass('disabled').removeAttr('disabled');
  controls.find('div[option-empty], option[option-empty]').attr('disabled', true).addClass('disabled');
  controls.find('li[option-empty], option[option-empty]').attr('disabled', true).addClass('disabled');
},

/**
 * Rebuild container
 *
 * @private
 */
_Rebuild: function() {

  var $widget = this,
    controls = $widget.element.find('.' + $widget.options.classes.attributeClass + '[attribute-id]'),
    selected = controls.filter('[option-selected]');

  // Enable all options
  $widget._Rewind(controls);

  // done if nothing selected
  if (selected.size() <= 0) {
    return;
  }

  // Disable not available options
  controls.each(function() {
    var $this = $(this),
      id = $this.attr('attribute-id'),
      products = $widget._CalcProducts(id);

    if (selected.size() == 1 && selected.first().attr('attribute-id') == id) {
      return;
    }

    $this.find('[option-id]').each(function() {
      var $this = $(this),
        option = $this.attr('option-id');

      if (!$widget.optionsMap.hasOwnProperty(id) || !$widget.optionsMap[id].hasOwnProperty(option) ||
        $this.hasClass('selected') ||
        $this.is(':selected')) {
        return;
      }

      if (_.intersection(products, $widget.optionsMap[id][option].products).length <= 0) {
        $this.attr('disabled', true).addClass('disabled');
      }
    });
  });
},

/**
 * Get selected product list
 *
 * @returns {Array}
 * @private
 */
_CalcProducts: function($skipAttributeId) {
  var $widget = this,
    products = [];

  // Generate intersection of products
  $widget.element.find('.' + $widget.options.classes.attributeClass + '[option-selected]').each(function() {
    var id = $(this).attr('attribute-id');
    var option = $(this).attr('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),
    options = _.object(_.keys($widget.optionsMap), {}),
    result;

  $widget.element.find('.' + $widget.options.classes.attributeClass + '[option-selected]').each(function() {
    var attributeId = $(this).attr('attribute-id'),
      selectedOptionId = $(this).attr('option-selected');

    options[attributeId] = selectedOptionId;
  });

  result = $widget.options.jsonConfig.optionPrices[_.findKey($widget.options.jsonConfig.index, options)];

  $productPrice.trigger(
    'updatePrice', {
      'prices': $widget._getPrices(result, $productPrice.priceBox('option').prices)
    }
  );

},

/**
 * Get prices
 * @param {Object} newPrices
 * @returns {Object}
 * @private
 */
_getPrices: function(newPrices, displayPrices) {
  var $widget = this;

  if (_.isEmpty(newPrices)) {
    newPrices = $widget.options.jsonConfig.prices;
  }

  _.each(displayPrices, function(price, code) {
    if (newPrices[code]) {
      displayPrices[code].amount = newPrices[code].amount - displayPrices[code].amount;
    }
  });

  return displayPrices;
},

/**
 * Gets all product media and change current to the needed one
 *
 * @private
 */
_LoadProductMedia: function() {
  var $widget = this,
    $this = $widget.element,
    attributes = {},
    productId = 0;

  if (!$widget.options.mediaCallback) {
    return;
  }

  $this.find('[option-selected]').each(function() {
    var $selected = $(this);
    attributes[$selected.attr('attribute-code')] = $selected.attr('option-selected');
  });

  if ($('body.catalog-product-view').size() > 0) {
    //Product Page
    productId = document.getElementsByName('product')[0].value;
  } else {
    //Category View
    productId = $this.parents('.product.details.product-item-details')
      .find('.price-box.price-final_price').attr('data-product-id');
  }

  var additional = $.parseParams(window.location.search.substring(1));

  $widget._XhrKiller();
  $widget._EnableProductMediaLoader($this);
  $widget.xhr = $.post(
    $widget.options.mediaCallback, {
      product_id: productId,
      attributes: attributes,
      isAjax: true,
      additional: additional
    },
    function(data) {
      $widget._ProductMediaCallback($this, data);
      $widget._DisableProductMediaLoader($this);
    },
    'json'
  ).done(function() {
    $widget._XhrKiller();
  });
},

/**
 * Enable loader
 *
 * @param $this
 * @private
 */
_EnableProductMediaLoader: function($this) {
  var $widget = this;

  if ($('body.catalog-product-view').size() > 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 $this
 * @private
 */
_DisableProductMediaLoader: function($this) {
  var $widget = this;

  if ($('body.catalog-product-view').size() > 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 $this
 * @param response
 * @private
 */
_ProductMediaCallback: function($this, response) {
  var isProductViewExist = $('body.catalog-product-view').size() > 0,
    $main = isProductViewExist ? $this.parents('.column.main') : $this.parents('.product-item-info'),
    $widget = this,
    images = [],
    support = function(e) {
      return e.hasOwnProperty('large') && e.hasOwnProperty('medium') && e.hasOwnProperty('small');
    };

  if ($widget._ObjectLength(response) < 1) {
    this.updateBaseImage(this.options.mediaGalleryInitial, $main, isProductViewExist);

    return;
  }

  if (support(response)) {
    images.push({
      full: response.large,
      img: response.medium,
      thumb: response.small
    });

    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, isProductViewExist);
},

/**
 * Update [gallery-placeholder] or [product-image-photo]
 * @param {Array} images
 * @param {jQuery} context
 * @param {Boolean} isProductViewExist
 */
updateBaseImage: function(images, context, isProductViewExist) {
  var justAnImage = images[0];

  if (isProductViewExist) {
    context
      .find('[data-gallery-role=gallery-placeholder]')
      .data('gallery')
      .updateData(images);
  } else if (justAnImage && justAnImage.img) {
    context.find('.product-image-photo').attr('src', justAnImage.img);
  }
},

/**
 * 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
 *
 * @private
 */
_EmulateSelected: function() {
  var $widget = this,
    $this = $widget.element,
    request = $.parseParams(window.location.search.substring(1));

  $.each(request, function(key, value) {
    $this.find('.' + $widget.options.classes.attributeClass + '[attribute-code="' + key + '"] [option-id="' + value + '"]').trigger('click');
  });
},

/**
 * Returns an array/object's length
 * @param obj
 * @returns {number}
 * @private
 */
_ObjectLength: function(obj) {
  var size = 0,
    key;

  for (key in obj) {
    if (obj.hasOwnProperty(key)) size++;
  }

  return size;
}
  });

My problems are:

Server reached "pm.max_children". Which is very strange for a server with one on it.

Error in browser console vendor/magento/module-customer/view/frontend/web/js/customer-data.js:

 getFromServer: function (sectionNames, updateSectionId) {
        sectionNames = sectionConfig.filterClientSideSections(sectionNames);
        var parameters = _.isArray(sectionNames) ? {sections: sectionNames.join(',')} : [];
        parameters['update_section_id'] = updateSectionId;
        return $.getJSON(options.sectionLoadUrl, parameters).fail(function(jqXHR) {
            throw new Error(jqXHR);
        });
    }

I get around this by increasing the pm.max_children and clearing cookies, cache in my browser. Then it works fine but it is not a solution.

I know this is pretty messed up and vague question. But suggestions or any help are appreciated very much. Thanks in advance.

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.