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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.