Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save Arifursdev/3136ce9e711800c2c6212220b7cfc980 to your computer and use it in GitHub Desktop.
Save Arifursdev/3136ce9e711800c2c6212220b7cfc980 to your computer and use it in GitHub Desktop.
Add a shipping rates calculator to your Shopify store

Add a shipping rates calculator to your Shopify store

requires: jQuery

estimate-shipping-calculator.js

window.estimateShippingCalculator = window.estimateShippingCalculator || {};

(function ($) {
    "use strict";

    window.estimateShippingCalculator = {
        init() {
            $('[data-adev-estimate-shipping]:not(.adev-init)').each((index, el) => {
                const self = $(el);
                self.addClass('adev-init');

                const calculate = self.find('[data-adev-es="calculate"]');
                const countrySelect = self.find('[data-adev-es="country"]');
                
                countrySelect.on('change', (e) => this.handleCountryChange(e, self));
                calculate.on('click', (e) => this.handleCalculateClick(e, self));
            });
        },
        createOptionElement(province) {
            const option = document.createElement('option');
            option.value = province[0];
            option.innerHTML = province[1];
            return option;
        },
        populateProvinces(provinces, provinceSelect) {
            provinceSelect.empty();
            provinces.forEach((province) => {
                provinceSelect.append(this.createOptionElement(province));
            });
        },
        handleCountryChange(e, self) {
            const provinceField = self.find('[data-adev-es="province-field"]');
            const provinceSelect = self.find('[data-adev-es="province"]');

            self.find('[data-adev-es="result"]').hide();

            const selectedCountryEl = $(e.target)[0];
            const selectedOption = selectedCountryEl[selectedCountryEl.selectedIndex];
            const provinces = JSON.parse(selectedOption.dataset.provinces || []);

            if (provinces && provinces.length === 0) {
                provinceField.hide();
            } else {
                this.populateProvinces(provinces, provinceSelect);
                provinceField.show();
            }
        },
        handleCalculateClick(e, self) {
            const provinceField = self.find('[data-adev-es="province-field"]');
            const countrySelect = self.find('[data-adev-es="country"]');
            const provinceSelect = self.find('[data-adev-es="province"]');
            const zip = self.find('[data-adev-es="zip"]');
            const result = self.find('[data-adev-es="result"]');

            e.preventDefault();

            result.removeClass('alert-error alert-success').hide();
            const params = `shipping_address[zip]=${zip.val()}&shipping_address[country]=${countrySelect.val()}&shipping_address[province]=${provinceSelect.val()}`;

            $(e.currentTarget).addClass('loading');

            $.ajax({
                url: `/cart/shipping_rates.json?${params}`,
                method: "GET",
                dataType: 'json',
                success: (data) => {
                    $(e.currentTarget).removeClass('loading');
                    this.displayShippingRates(result, data)
                },
                error: (error) => {
                    $(e.currentTarget).removeClass('loading');
                    this.displayErrors(result, error.responseJSON)
                },
            });
        },
        displayShippingRates(element, data) {
            if (data.shipping_rates && data.shipping_rates.length) {
                const headingLocaleStr = data.shipping_rates.length === 1 ? 'There is one shipping rate for this destination:' : 'There are multiple shipping rates for this destination:';
                let rates = '';

                data.shipping_rates.forEach((rate) => {
                    const formattedRate = theme.moneyFormat.replace(/\{\{\s*(\w+)\s*\}\}/, rate.price);
                    rates += `<li>${rate.name}: ${formattedRate}</li>`;
                });

                element.addClass('alert-success').html(`<p>${headingLocaleStr}</p><ul class="styled-list">${rates}</ul>`).show()
            } else {
                element.addClass('alert-success').html('<p>We do not ship to this destination.</p>').show()
            }
        },
        displayErrors(element, data) {
            let errors = '';

            Object.keys(data).forEach((key) => {
                errors += `<li>${data[key]}</li>`;
            });

            element.addClass('alert-error').html(`<ul class="styled-list">${errors}</ul>`).show();
        }
    };

    document.addEventListener('adev:estimate:shipping:init', () => {
        window.estimateShippingCalculator.init();
    });

    document.dispatchEvent(new CustomEvent('adev:estimate:shipping:init'));

})(jQuery);

estimate-shipping-calculator.liquid

<script src="{{ 'estimate-shipping-calculator.js' | asset_url }}" defer></script>
<div class="adev-estimate-shipping" data-adev-estimate-shipping>

    <div class="adev-es__heading">Estimate Shipping</div>

    <div class="adev-es__form">

        <div class="adev-es__result alert" data-adev-es="result"></div>

        <div class="adev-es__field">
            <label for="adev-es-country">Country</label>
            <select id="adev-es-country" data-adev-es="country" name="address[country]" data-default="{% if shop.customer_accounts_enabled and customer %}{{ customer.default_address.country }}{% endif %}">{{ country_option_tags }}</select>
        </div>

        <div class="adev-es__field" data-adev-es="province-field" style="display: none;">
            <label for="adev-es-province">Province</label>
            <select id="adev-es-province" data-adev-es="province" name="address[province]" data-default="{% if shop.customer_accounts_enabled and customer and customer.default_address.province != '' %}{{ customer.default_address.province }}{% endif %}"></select>
        </div>

        <div class="adev-es__field">
            <label for="adev-es-zip">Zip Code</label>
            <input type="text" id="adev-es-zip" data-adev-es="zip" name="address[zip]"{% if shop.customer_accounts_enabled and customer %} value="{{ customer.default_address.zip }}"{% endif %}>
        </div>

        <div class="adev-es__field maring-0">
            <button type="button" class="btn block adev-es__calculate" data-adev-es="calculate">
                <span>Calculate</span>
            </button>
        </div>

    </div>

</div>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment