6

I'm trying to have swatch option values selected on the Product View page, if only one value exists.

If the user selects a product from the Category page, the product page displays with the attributes Color, Shape, and Size. Some products only have one color, one shape, and maybe two sizes. I want the color and shape values to be automatically selected when that page loads.

Thanks!

2 Answers 2

6

I had a similar task today - auto select size attribute, if there is only one option. Here is what I did:

In /Magento_Swatches/web/js/SwatchRenderer.js, in the _RenderControls function

lines 272-273 (in the end of $.each(this.options.jsonConfig.attributes) loop) add

if(item.options.length==1)
$widget.theOption = $widget.element.find('[attribute-id=' + item.id + '] .swatch-option')[0];

Here I just take my single size div element that should be selected

And then line 294 (at the bottom of the function):

if($widget.theOption != undefined) $widget.theOption.click();

Trigger click event to select size option

3
  • Simple and effective.
    – thaddeusmt
    Commented Dec 15, 2017 at 21:58
  • I do not find that SwatchRenderer.js file in Magento 2.2.5
    – hey
    Commented Jul 18, 2018 at 3:26
  • @Anna not working in magento 2.3.1 Commented Jun 15, 2019 at 12:17
0
   /**
     * 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 $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();
    },

    /**
     * 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('div[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;
    }
1
  • Why is there so much code here? What have you changed in the default swatch-renderer file? How do I port this to 2.2 (assuming this is the 2.1 version of swatch-renderer being modified? This answer need a lot of work to be helpful.
    – thaddeusmt
    Commented Dec 15, 2017 at 21:35

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.