Context
I'm using the cocoon gem to create nested invoice_rows
for invoice
. Everything works as expected (deleting, creating nested records etc.).
In the invoice form
I am also keeping tracking of the most actual total_invoice amount
with Javascript. With my Javascript code I am able to recalculate the total_invoice amount
when the price, quantity or VAT
changes or when a new invoice_row
is created with cocoon.
Issue
The issue occurs when I delete a nested record. Following the documentation, I'm trying to use cocoon:after-remove
to trigger the event, but nothing gets triggered after I delete an invoice_row
in my view.
Other attempt
I also tried a click event on the delete button (see also the code commented out in the script). Unfortunately, the class of the deleted nested record is still picked up by my Javascript code and consequently is entered in the calculation of the total_invoice amount
. (hence the cocoon:after-remove
command I guess)
Code
invoice_form.html.erb
<div class="form-container col col-sm-6 col-lg-12">
<%= simple_form_for [@hotel, @invoice] do |f|%>
<h5><strong><u><%= t('.invoice') %> </u></strong></h5>
<!-- headers -->
<div class="row">
<div class="col col-sm-3"><b>description</b></div>
<div class="col col-sm-2"><b>unit price</b></div>
<div class="col col-sm-2"><b>amount</b></div>
<div class="col col-sm-2"><b>VAT (%)</b></div>
<div class="col col-sm-2"><b>Total</b></div>
</div>
<div class="border-invoice"></div>
<!-- headers -->
<%= f.simple_fields_for :invoice_rows do |invoice_row| %>
<div class="reservation-details">
<%= render 'invoice_row_fields', f: invoice_row %>
</div>
<% end %>
<div id="check">
<%= link_to_add_association f, :invoice_rows do %>
<div class="option-add-option-price">
<div class="prices-border">
<i class="fas fa-plus"></i> Add another invoice line
</div>
</div>
<% end %>
</div>
<div class="border-invoice"></div>
<div class="row">
<div class="col-sm-8"></div>
<div class="col-sm-1"><b>Subtotal</b></div>
<div class="col col-sm-2"><input type="text" class="field nettotal form-control"></div>
</div>
<br>
<div class="row">
<div class="col-sm-8"></div>
<div class="col-sm-1">VAT</div>
<div class="col col-sm-2"><input type="text" class="field vat-total form-control"></div>
</div>
<br>
<div class="row">
<div class="col-sm-8"></div>
<div class="col-sm-1"><b>Total</b></div>
<div class="col col-sm-2"><input type="text" class="field gross-total form-control"></div>
</div>
<div class="row">
<div class="col col-sm-6"> <%= f.button :submit, t(".invoice_button"), class: "create-reservation-btn"%>
</div>
</div>
<% end %>
</div>
script for invoice form
// $(document).on('click', '.delete-vat', function() { /* recalculate */
$(document).ready(function(){
$('#check')
.on('cocoon:after-remove', function() { /* recalculate */
var result = 0
var vat_result = 0
var price = [];
var quantity = [];
var vat = [];
console.log('yes')
$('.price').each(function(i, obj) {
price.push((Math.round(+obj.value*100)/100).toFixed(2));
});
$('.quantity').each(function(i, obj) {
quantity.push(+obj.value);
});
$('.vat').each(function(i, obj) {
vat.push(+obj.value);
});
var result = 0
price.forEach((o,i)=>{
$(".gross-total").eq( i ).val(o*quantity[i]);
result += o*quantity[i];
// console.log(result)
$(".gross-total").val(result);
});
var vat_result = 0
price.forEach((o,i)=>{
$(".vat-total").eq( i ).val(o*vat[i]);
vat_result += o*vat[i]/100 * quantity[i];
$(".vat-total").val(vat_result);
});
var sub = result - vat_result
$(".nettotal").val(sub);
})
_invoice_row_fields.html.erb
<div class="nested-fields">
<div class="row test">
<div class="col col-sm-3"><%= f.input :description, placeholder: "Product or service description", label: false %></div>
<div class="col col-sm-2"><%= f.input :price, placeholder: "Price incl. VAT", label: false, input_html:{class: "field price"} %></div>
<div class="col col-sm-2 "><%= f.input :amount, label: false, input_html:{class: "field quantity"} %></div>
<div class="col col-sm-2"><%= f.collection_select :vat_percentage, @hotel.vat_groups, :vat_percentage, :vat_percentage, {prompt: "Select a VAT"}, {class: "form-control vat"} %></div>
<div class="col col-sm-2"><input type="text" class="field subtotal form-control"></div>
<div class="col col-sm-1">
<%= link_to_remove_association f do %>
<i class="fas fa-trash delete-vat"></i>
<% end %>
</div>
</div>
</div>
When removing items using cocoon, they are in general not really removed, just hidden. This is because the form (and all changes), are only saved when posting the form to the server.
So if you added a nested item, without saving the form, and then remove it, it is physically removed from the form (DOM). However, if a once-saved nested item is removed, it needs to be hidden, and the _destroy
flag set, so the server knows which items to remove.
So the handle this correctly in your javascript, you can use the :visible
selector. E.g. do something like
$('.price:visible').each(function() { ...
Furthermore, your cocoon:after-remove
is now registered on the #check
div? You should register it on a surrounding div of the nested items, e.g. the form
itself would work better imho.