1

I am following this guide for creating a shopping cart model: https://richonrails.com/articles/building-a-shopping-cart-in-ruby-on-rails

I got it to work successfully, but still have a problem. When I load the page, and add an item, one is added, if I go to another page, and then load the home page again, through a side bar menu I have, I click a product, and that same product gets added 3 times to the shopping cart. I go to another page and return, 5 items per click, again 7 items per click. I have no idea why this is happening, I don't even know what part of my code to show, so someone can help me. If I reload the page (by clicking the address bar and enter), it goes back to adding one item per click.

Thanks in advance

EDIT: After first comment suggestion, here is the controller code.

def create
    @invoice = current_invoice
    @invoice_product = @invoice.invoice_products.new(invoice_product_params)
    @invoice.save
    session[:invoice_id] = @invoice.id
end

def update
    @invoice = current_invoice 
    @invoice_product = @invoice.invoice_products.find(params[:id])
    @invoice_product.update.attributes(order_item_params)
    @invoice_products = @invoice.invoice_products
end

def destroy
    @invoice = current_invoice 
    @invoice_product = @invoice.invoice_products.find(params[:id])
    @invoice_product.destroy
    @invoice_products = @invoice.invoice_products
end

private
def invoice_product_params
    params.require(:invoice_product).permit(:id, :invoice_id, :product_id, :price, :tax, :value)
end
Fermin
  • 473
  • 1
  • 7
  • 19
  • 2
    Controller code would be a good start! – RichardAE Oct 08 '15 at 08:38
  • I found that moving <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> from body to head in application.html.erb worked, but that makes my sidebar menu stop working. For some reason, the js event gets reattached on every request if it is on the body. Any help? – Fermin Oct 08 '15 at 09:17
  • 1
    Revert back that line and remove Turbolinks and give it a try: http://blog.steveklabnik.com/posts/2013-06-25-removing-turbolinks-from-rails-4 – RichardAE Oct 08 '15 at 10:38
  • I dislike Turbolinks anyway but if you do want to use it again at least we can narrow down the issue. – RichardAE Oct 08 '15 at 10:39
  • Moved it back to body and removed turbolinks, the line now looks like this: <%= javascript_include_tag 'application' %> however, the problem still persists – Fermin Oct 08 '15 at 16:01
  • Sorry, hadn't followed your link. Removing turbolinks worked. How bad is it to remove it? Should I continue investigating on how to make it work with it, or without it is ok? – Fermin Oct 08 '15 at 16:06

1 Answers1

2

that same product gets added 3 times to the shopping cart. I go to another page and return, 5 items per click, again 7 items per click

This has all the hallmarks of Turbolinks and bad JS binding.

--

Let me explain...

Turbolinks makes following links in your web application faster. Instead of letting the browser recompile the JavaScript and CSS between each page change, it keeps the current page instance alive and replaces only the body (or parts of) and the title in the head. Think CGI vs persistent process.

In short, Turbolinks uses "Ajax" to load the <body> of the next page, replacing your current <body> content. Whilst this does speed up processing (by removing the need to recompile CSS/images), it causes havoc with JS bindings.

JS "binds" to elements in the DOM:

enter image description here

It expects there to be elements for it to bind to. This works very well in most cases:

element = document.getElementById("your_element").addEventListener('click', function() {
   console.log('anchor');
});

However, the problem with using Turbolinks (and especially JQuery) is the binding can occur multiple times depending on how many times Turbolinks just loads new data into the DOM.

The issue is because your Javascript is not refreshing, but your DOM elements are, JS is treating them like new elements, thus triggering the function x number of times with each click. Kind of like n+1 I suppose.

--

In answer to your problem, the issue will lie with your JS bindings:

#app/assets/javascripts/application.js
bind = function() {
    $("#shopping_cart").on("click", function(){
        //payload
    });
};
$(document).on("ready page:load", bind);

The above will you the "local" selection for the elements, and using the page:load Turbolinks hook, will make sure it refreshes each time Turbolinks is requested.

If you wanted to do it without having to redeclare each time Turbolinks is called, just delegate from the document:

#app/assets/javascripts/application.js
$(document).on("click", "#shopping_cart", function(){
   //payload
});
Community
  • 1
  • 1
Richard Peck
  • 76,116
  • 9
  • 93
  • 147
  • As per comment by RichardAE, removing turbolinks worked, making your explanation totally correct. However, I do not know how to implement your solution on my rails app, since the javascript binding is done by rails. It is a form_for with remote:true, and it calls create.js.erb on the submit. I never explicitly tell it to bind the code in create.js.erb to the submit button in that form. – Fermin Oct 08 '15 at 16:10