function in cart triggering too many times

njwfxnjwfx Member
I am trying to create some logic to add a free item to the cart if there is a particular item in the cart.

The code I wrote works fine, except that I kept getting a message saying that 2 was greater than the quantity allowed (I had set a max quantity of 1 on the free item)

So I wrote a very simple function

{% if context == "cart" %}
<script>

FC.client.on("cart-item-remove.done", checkFreeProduct);
FC.client.on("cart-submit.done", checkFreeProduct);
FC.client.on("cart-item-quantity-update.done", checkFreeProduct);

function checkFreeProduct() {
alert ("in checkFreeProduct function on cart")
}
</script>
{% endif %}


With this I could track how often my function 'checkFreeProduct' was being called.

I added the code into the 'custom footer' in the 'Add custom header and footer code to your templates' section on the 'configuration' page

I have a 'view cart' link on my product pages.

Clicking on 'view cart', opens the side cart and I get TWO alert messages - so the function is being called twice. Why is this the case?

At this point there are no items in the cart and all I'm doing is clicking on 'view cart'

If I then click on the 'keep shopping' link and again click on the 'view cart' link I get FOUR alert messages - so my function is now being called FOUR times.

Every time I click on 'keep shopping' and then 'view cart' again the number of alert messages increases by TWO.

If I click on 'Proceed to checkout', then 'continue shopping', then 'view cart', the number of alert messages drops back to TWO

Can you advise what may be wrong and how I can get a function to only trigger once when I 'view cart', 'submit cart', 'update quantity' or 'remove item'

Thanks


Comments
  • fc_adamfc_adam FoxyCart Team
    @njwfx,

    Thanks for posting with a detailed overview - and sorry for the confusion here.

    Currently, with how the custom footer code is included on the sidecart, when it is re-rendered, it causes the javascript to be executed as second time. With a single view cart call, the sidecart is rendered twice - which is why you're seeing the function called twice, and then increased by two each time you close and show the sidecart. We have a ticket for correcting this - but unfortunately haven't had a chance to work on it yet.

    For now - you could fix this by not including the custom code on the sidecart from the custom footer - instead including it from your website. It does mean there would be a double-up in your custom logic, but should help get it processing just the once.

    In your custom footer, you could instead structure it like this:
    {% if context == "checkout" or cart_is_fullpage %}
    // your code here
    {% endif %}
    And then on your own website, include your code like this:
    <script>
    var FC = FC || {};
    FC.onLoad = function() {
    // Your code here
    };
    </script>
    That should ensure that on the sidecart, the callback functions are only added to the events once - no matter how many times you interact with sidecart on the same page without reloading.
  • njwfxnjwfx Member
    great - thanks.

    That works as you suggested, when I put the code on my own website for the sidecart and put the same code in the custom footer for the checkout.

    I have two more questions

    1.
    I already had a function on my website using FC.onload

    When I add the new function they don't co-exist - so the later one overwrites the earlier one.

    The function I already had is only on the product pages as it was code on cart-submit
    The new function needs to be on all pages as the sidecart can be accessed from all pages and once they are in the side cart they can remove items.

    Can I 'predefine' the FC var and then access FC.onload from two different scripts?

    2. Does it matter if I render the cart more than once in my code

    So I add a free item if they're eligible - and that has a cart render in it
    Then I check if they actually already had an item of the free type (at full price) in the cart.
    If they did then I either remove it or reduce the quantity by 1.
    reducing the quantity requires another cart render - can I just do a cart render at the end somehow?

    Here's my code

    if (mc_product_qty > 0 && mc_free_item_id == 0) {
    // They should have the free item, let's add it
    FC.client.request("https://" + FC.settings.storedomain + "/cart?" + mc_free_item_url) .done(function(dataJSON) {
    FC.cart.render();
    });
    // if they already had a paid item of the free type in the cart - reduce the qty by 1.
    if (mc_paid_item_id > 0) {
    mc_new_qty = mc_paid_item_qty - 1;
    if (mc_new_qty <= 0){
    FC.cart.removeItem({ id: mc_paid_item_id });
    } else {
    FC.client.request('https://'+FC.settings.storedomain+'/cart?cart=update&id='+ mc_paid_item_id +'&quantity=' + mc_new_qty).done(function(dataJSON) {
    FC.cart.render();
    });
    }
    }
    }


    Thanks

  • fc_adamfc_adam FoxyCart Team
    @njwfx,

    I'm glad that helped!
    Can I 'predefine' the FC var and then access FC.onload from two different scripts?
    Good question in regards to the onLoad functions. You can allow it to support defining multiple callbacks by taking an approach like shown on this link: https://pastebin.com/raw/eTQyyG4e. You can add additional functions to the onLoadCallbacks array and the normal onLoad function will execute all of them.
    2. Does it matter if I render the cart more than once in my code
    Your approach should be fine there. One improvement you might want to make is to pull the second block into the callback of the first - and then you could get it just rendering once, like this:
    if (mc_product_qty > 0 && mc_free_item_id == 0) {
    // They should have the free item, let's add it
    FC.client.request("https://" + FC.settings.storedomain + "/cart?" + mc_free_item_url) .done(function(dataJSON) {
    // if they already had a paid item of the free type in the cart - reduce the qty by 1.
    if (mc_paid_item_id > 0) {
    mc_new_qty = mc_paid_item_qty - 1;
    if (mc_new_qty <= 0){
    FC.cart.removeItem({ id: mc_paid_item_id });
    } else {
    FC.client.request('https://'+FC.settings.storedomain+'/cart?cart=update&id='+ mc_paid_item_id +'&quantity=' + mc_new_qty).done(function(dataJSON) {
    FC.cart.render();
    });
    }
    } else {
    FC.cart.render();
    }
    });
    }
  • njwfxnjwfx Member
    Thanks for the suggestion:
    You can allow it to support defining multiple callbacks by taking an approach like shown on this link: https://pastebin.com/raw/eTQyyG4e. You can add additional functions to the onLoadCallbacks array and the normal onLoad function will execute all of them.
    I don't think that will help as I wanted one function to exist only on a subset of pages and the other function to exist on all pages. It seems the code you've suggested would load both functions on any page it was included.

    If I have to have both functions in all pages then I may as well just include both functions into the .onload as :


    FC.client.on('cart-submit', function(params, next) {
    $element = $(params.element);
    // my code here
    });


    FC.client.on("cart-item-remove.done", checkFreeProduct);
    FC.client.on("cart-submit.done", checkFreeProduct);
    FC.client.on("cart-item-quantity-update.done", checkFreeProduct);

    function checkFreeProduct() {
    // my code here
    };


    This seems to work correctly, unless you can see an issue with it

    Your suggested rewrite of my code to pull my second block of code into the callback of the first is a perfect solution. I just wasn't sure what could be included in a callback - so thanks for the heads up
  • fc_adamfc_adam FoxyCart Team
    @njwfx,

    Thanks for the additional context here - the approach I've detailed should be able to assist with your needs here - but the example code I've provided may not have been the best example to match what you're after.

    With the approach I described, you should be fine to output functions conditionally on different pages through different files.

    For example, you could define the free product code in one file that is included on all pages, and it would begin the FC object definition and define the onLoad like this:
    var FC = FC || {};
    FC.onLoadCallbacks = [];
    FC.onLoadCallbacks.push(function() {
    console.log("callback 1");
    });
    FC.onLoad = function() {
    if (FC.hasOwnProperty("onLoadCallbacks") && typeof FC.onLoadCallbacks == "object" && FC.onLoadCallbacks.length > 0) {
    for (var i = 0; i < FC.onLoadCallbacks.length; i++) {
    if (typeof FC.onLoadCallbacks[i] == "function") {
    FC.onLoadCallbacks[i]();
    }
    }
    }
    }
    Then in a separate file that you only include on the product pages after the file that includes the above code, you could define your other callback like this:

    if (typeof FC == "object" && FC.hasOwnProperty("onLoadCallbacks") {
    FC.onLoadCallbacks.push(function() {
    console.log("callback 2");
    });
    }
    That should work for what you're after.

    That said - there also shouldn't be any issue with also having all of the functions defined on every page, within a single onLoad function call.
  • njwfxnjwfx Member
    Thanks so much for your excellent support

    The code you've suggested is great
Sign In or Register to comment.