Site logo

Archived topic

generateQuantityButtons() with 1000+ products on the same page

7 replies · Started by Colin on March 10, 2021

Viewing posts 1–8 of 8

I have a large inventory category page with 1000+ page and the generateQuantityButtons functionality is crippling the loading time.

Would it be possible for you to add hooking ability to various functionality in the plugin, it could work by declaring the the following and then checking for their existence in the plugin core JS.

generateWooCommerce.hooks.generateQuantityButtons
generateWooCommerce.selectors.generateQuantityButtons.quantityBoxes
generateWooCommerce.callbacks.generateQuantityButtons.quantityBoxes

File: gp-premium/woocommerce/functions/js/woocommerce.js
176: generateQuantityButtons()
185: quantityBoxes = $( '.cart div.quantity:not(.buttons-added), .cart td.quantity:not(.buttons-added)' ).find( '.qty' );
188: $.each( quantityBoxes, function( key, value )

Hi there,

Is this a single page with 1000+ products on it?

If so, the JS adding the buttons is likely going to be an issue no matter what - that's a ton of products to loop through.

It would be better if the buttons were added in HTML instead of JS, which is something we're working on.

Thanks for the reply Tom.

I'm aware it's less than ideal but the request is to avoid pagination on this page. The functionality of the .each callback is fairly heavy and could be optimised significantly if there was a callback / hook I could use.

I'm not a huge fan of the current implementation, either. It could definitely be significantly improved.

What exactly would you do with the callback/hook? Can you show me an example of how it would help?

Tom,

I've resolved the issue and significantly optimised it. The problem was mostly down to the jQuery selectors inside the .each were not scoped to the current quantity input field in iteration. The .each isn't async and simply blocks rendering.

Speeds have reduced from 30 seconds down to 1-2 seconds.

See updated source below:


function generateQuantityButtons() {
        // Check if we have an overwrite hook for this function
        try {
            return generateWooCommerce.hooks.generateQuantityButtons();
        } catch(e) {
            // No hook in place, carry on
        }

        // Grab the FIRST available cart form on the page
        let cart = $('.woocommerce div.product form.cart').first();

        // Check if we see elementor style classes
        if (cart.closest('.elementor-add-to-cart').length) {
            // Found classes, remove them and finish here
            $('.elementor.product').removeClass('do-quantity-buttons');
            return;
        }

        // Grab all the quantity boxes that need dynamic buttons adding
        let quantityBoxes;
        try {
            // Is there a hook available?
            quantityBoxes = generateWooCommerce.selectors.generateQuantityButtons.quantityBoxes;
        } catch(e) {
            // Use the default plugin selector functionality
            quantityBoxes = $('.cart div.quantity:not(.buttons-added), .cart td.quantity:not(.buttons-added)').find('.qty');
        }
        // Test the elements have length and greater than 0
        // Try, catch here to provide basic error checking on hooked data
        try {
            // Nothing found... stop here
            if (quantityBoxes.length === 0) return false;
        } catch(e) {
            console.log(e);
            return false;
        }

        // Allow the each loop callback to be completely overwritten
        let quantityBoxesCallback;
        try {
            // Try assign a hooked callback
            quantityBoxesCallback = generateWooCommerce.callbacks.generateQuantityButtons.quantityBoxes;
        } catch(e) {
            // Use the default callback handler
            quantityBoxesCallback = function (key, value) {
                let box = $(value);
                // Check allowed types
                if (['date', 'hidden'].indexOf(box.prop('type')) !== -1) return;

                // Add plus and minus icons
                box.parent().addClass('buttons-added').prepend('<a href="javascript:void(0)" class="minus">-</a>');
                box.after('<a href="javascript:void(0)" class="plus">+</a>');

                // Enforce min value on the input
                let min = parseFloat($(this).attr('min'));
                if (min && min > 0 && parseFloat($(this).val()) < min) {
                    $(this).val(min);
                }

                // Add event handlers to plus and minus (within this scope)
                box.parent().find('.plus, .minus').on('click', function () {
                    // Get values
                    let currentQuantity = parseFloat(box.val()),
                        maxQuantity = parseFloat(box.attr('max')),
                        minQuantity = parseFloat(box.attr('min')),
                        step = box.attr('step');

                    // Fallback default values
                    if (!currentQuantity || '' === currentQuantity || 'NaN' === currentQuantity) {
                        currentQuantity = 0;
                    }

                    if ('' === maxQuantity || 'NaN' === maxQuantity) {
                        maxQuantity = '';
                    }

                    if ('' === minQuantity || 'NaN' === minQuantity) {
                        minQuantity = 0;
                    }

                    if ('any' === step || '' === step || undefined === step || 'NaN' === parseFloat(step)) {
                        step = 1;
                    }

                    // Change the value
                    if ($(this).is('.plus')) {

                        if (maxQuantity && (maxQuantity == currentQuantity || currentQuantity > maxQuantity)) {
                            box.val(maxQuantity);
                        } else {
                            box.val(currentQuantity + parseFloat(step));
                        }

                    } else {

                        if (minQuantity && (minQuantity == currentQuantity || currentQuantity < minQuantity)) {
                            box.val(minQuantity);
                        } else if (currentQuantity > 0) {
                            box.val(currentQuantity - parseFloat(step));
                        }

                    }

                    // Trigger change event
                    box.trigger('change');
                });
            }
        }

        $.each(quantityBoxes, quantityBoxesCallback);
    }

I've added in the hooking ability so in the theme file such as template-fixes.js you can add any overwrites such as the following as a very basic example.

generateWooCommerce.callbacks = {
        generateQuantityButtons: {
            quantityBoxes: function(key, value) {
                $(value).css('background-color', 'red');
            }
        }
    };

Wow - this is great, thank you! Will test it and if all checks out I'll get it added to 2.0.0.

Out of curiosity, what other kind of use-cases would you have for the callback? Being able to overwrite the function is definitely cool, but I wonder why one would want to?

My pleasure and please do let me know if you add it to the next release so I can remove my makefile / patch for the build.

Re: use-cases
If I needed to make a completely new set of incremental buttons / quantity boxes for certain "special" products then I could still use your plugin along side my bespoke functionality and not worry about either
1. completely disabling GeneratePress functionality,
2. having to copy your js into a separate theme/plugin to work with the bespoke code but for it to fail when GeneratePress has major changes at the core

More examples may be:
1. When you click the incremental buttons have a modal/popup appear
2. Completely disable / lock quantity selections on certain products
3. Flashing animations on certain quantity products
4. the list goes on...

It's more about allowing others to use the plugin along side their customisations and know it will survive when your (GeneratePress) theme/plugin is updated.

I guess the easiest model to follow would be Wordpress itself where they allow you add actions, filters to hook into various aspects of the code for customisation.

I hope this helps!

Perfect. Added in 2.0.0-alpha.4 (just released).

Thank you!

This archived topic is closed to new replies.