Skip to main content
deleted 7 characters in body
Source Link
Siarhey Uchukhlebau
  • 16.2k
  • 11
  • 55
  • 88

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

   /**
     var* $widgetEvent =listener
 this;    *
     * @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;
    }
});

});

/** * 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;
    }
});

});

   /**
     * 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;
    }
Source Link

/** * 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;
    }
});

});