Sterling Rose Design Blog
Calculating Line Item Extensions
Authored by Dana Jones
May 25, 2009 14:48
0 Comments
Tags: Javascript Rails Prototype AJAX
Leave a Comment
Authored by Dana Jones
May 25, 2009 14:48
0 Comments
Tags: Javascript Rails Prototype AJAX
In my project, I have orders, and each order can have an unlimited number of line_items. Line_items are created by the user clicking on a button, which appends (via RJS) a new row to the line_items tabled form. So far, so good.
But I needed the extended price (quantity * price_per) of each line_item to be calculated every time the user tabbed out or clicked away from the price_per field. Further, I needed the subtotal, tax, total, and balance fields to be automatically re-calculated.
I messed around with it for several hours, trying Javascript, Prototype, and even jQuery, before I finally settled on a Prototype approach that worked. The real struggle was that using Rails 2.3’s nested forms functionality meant that each line_item would have an index key embedded in the middle of the text field’s name and id, and I could not come up with a good way to extract it, to pass it to the Javascript function.
Luckily, the numbering appears to be a 0-based index, so I cheated and used an incrementer.
I know it’s not a perfect solution (it’s obtrusive, it’s probably much more verbose than it needs to be) but it functions.
So, on to the code. Obviously, I’ve snipped out a lot of stuff that doesn’t have anything to do with this example.
Yeah, I called my class “fluffy_bunny.” It’s not a class name I was likely to use anywhere else, and fluffy bunnies are cute. :)
CommentsBut I needed the extended price (quantity * price_per) of each line_item to be calculated every time the user tabbed out or clicked away from the price_per field. Further, I needed the subtotal, tax, total, and balance fields to be automatically re-calculated.
I messed around with it for several hours, trying Javascript, Prototype, and even jQuery, before I finally settled on a Prototype approach that worked. The real struggle was that using Rails 2.3’s nested forms functionality meant that each line_item would have an index key embedded in the middle of the text field’s name and id, and I could not come up with a good way to extract it, to pass it to the Javascript function.
Luckily, the numbering appears to be a 0-based index, so I cheated and used an incrementer.
I know it’s not a perfect solution (it’s obtrusive, it’s probably much more verbose than it needs to be) but it functions.
So, on to the code. Obviously, I’ve snipped out a lot of stuff that doesn’t have anything to do with this example.
/controllers/orders_controller.rb
def new
@order = Order.new
@order.line_items.build
@order.zero_set_amounts
@colors = Color.unremoved
end
def edit
@order = Order.new
@colors = Color.unremoved
end/models/order.rb
def zero_set_amounts
self.subtotal = 0
self.tax = 0
self.shipping = 0
self.artwork = 0
self.setup = 0
self.printing_charge = 0
self.misc_charge = 0
self.total = 0
self.balance = 0
self.reorder ||= false
end/views/layouts/application.html.erb
<head>
<%= javascript_include_tag :defaults, 'main' %>
</head>/views/orders/new.html.erb and /views/orders/edit.html.erb
<% form_for(@order) do |f| %>
<%= render :partial => "form", :locals => {:f => f} %>
<% end %>/views/orders/_form.html.erb
<div id="order_line_items">
<%= render :partial => 'line_items', :locals => {:f => f, :order => @order} %>
<div id="order_summary_fields">
<div class="left"><%= f.label :subtotal %></div>
<div class="right"><%= text_field_tag :subtotal, 0, :size => 10,
:disabled => true %></div>
<div class="left"><%= f.label :artwork %></div>
<div class="right"><%= f.text_field :artwork, :size => 10, :onblur =>
'recalculateTotals();' %></div>
<div class="left"><%= f.label :setup %></div>
<div class="right"><%= f.text_field :setup, :size => 10, :onblur =>
'recalculateTotals();' %></div>
<div class="left"><%= f.label :printing_charge, "Printing" %></div>
<div class="right"><%= f.text_field :printing_charge, :size => 10, :onblur =>
'recalculateTotals();' %></div>
<div class="left"><%= f.label :misc_charge, "Miscellaneous" %></div>
<div class="right"><%= f.text_field :misc_charge, :size => 10, :onblur =>
'recalculateTotals();' %></div>
<div class="left"><%= f.label :shipping %></div>
<div class="right"><%= f.text_field :shipping, :size => 10, :onblur =>
'recalculateTotals();' %></div>
<div class="left"><%= f.label :tax %></div>
<div class="right"><%= text_field_tag :tax, 0, :size => 10, :disabled => true %></div>
<div class="left"><strong><%= f.label :total %></strong></div>
<div class="right"><%= text_field_tag :total, 0, :size => 10,
:disabled => true %></div>
<div class="left"><%= f.label :balance %></div>
<div class="right"><%= text_field_tag :balance, 0, :size => 10,
:disabled => true %></div>
<%= render :partial => "shared/create_or_update", :locals => {:f => f} %>
<%= f.hidden_field :subtotal %>
<%= f.hidden_field :tax %>
<%= f.hidden_field :total %>
<%= f.hidden_field :balance%>
<%= f.hidden_field :reorder %>
</div>
</div>/views/order/_line_items.html.erb
<%= button_to_remote 'Add a New Line Item', {:url => new_line_item_path, :method => :get} %>
...
<% i = 0 %>
<% f.fields_for :line_items do |line_items_forms| %>
<tr>
<td><%= line_items_forms.text_field :quantity, :size => 5 %></td>
<td><%= line_items_forms.text_field :description, :size => 30 %></td>
<td><%= line_items_forms.select :color_id, @colors.map {|c| [c.name, c.id]} %></td>
<td><%= line_items_forms.text_field :price_per, :size => 10, :onblur =>
order.new_record? ? 'updateExtendedPrice("order_line_items_attributes_0_quantity",
this.id, "order_line_items_attributes_0_price_extended");' :
"updateExtendedPrice('order_line_items_attributes_#{i}_quantity', this.id,
'order_line_items_attributes_#{i}_price_extended');" %></td>
<td><%= line_items_forms.text_field :price_extended, :size => 10, :class =>
'fluffy_bunny' %></td>
<% i += 1 %>
</tr>
<% end %>Yeah, I called my class “fluffy_bunny.” It’s not a class name I was likely to use anywhere else, and fluffy bunnies are cute. :)
/controllers/line_items_controller.rb
def new
begin
@order = Order.find(params[:order_id])
@line_item = @order.line_items.build
rescue ActiveRecord::RecordNotFound
@line_item = LineItem.new
ensure
@item_index = @order.nil? ? Time.now.to_i : (@order.line_items.length - 1)
end
respond_to do |format|
format.html
format.js do
@colors = Color.unremoved.collect {|c| [c.name, c.id]}
@line_id = "order_line_items_attributes_#{@item_index}"
@line_name = "order[line_items_attributes][#{@item_index}]"
end
end
end/views/line_items/new.js.erb
$('line_item_table').insert(<%=js render(:partial => "new_line_item") %>, 'bottom');/views/line_items/_new_line_item.html.erb
<tr>
<td><%= text_field_tag "#{@line_id}_quantity", '', :name => "#{@line_name}[quantity]",
:size => 5 -%></td>
<td><%= text_field_tag "#{@line_id}_description", '', :name =>
"#{@line_name}[description]", :size => 30 -%></td>
<td><%= select_tag "#{@line_id}_color_id", options_for_select(@colors), :name =>
"#{@line_name}[color_id]" -%></td>
<td><%= text_field_tag "#{@line_id}_price_per", '', :name =>
"#{@line_name}[price_per]", :size => 10, :onblur =>
"updateExtendedPrice('order_line_items_attributes_#{@item_index}_quantity', this.id,
'order_line_items_attributes_#{@item_index}_price_extended');" -%></td>
<td><%= text_field_tag "#{@line_id}_price_extended", '', :name =>
"#{@line_name}[price_extended]", :size => 10, :class => 'fluffy_bunny' -%></td>
</tr>/public/javascripts/main.js
function updateExtendedPrice(x,y,z) {
$(z).value=parseFloat($(x).value) * parseFloat($(y).value);
updateSubtotal();
recalculateTotals();
}
function updateSubtotal() {
var subtotal=0;
var li;
var line_item_extensions = $$('.fluffy_bunny');
line_item_extensions.each(function(li) {subtotal += parseFloat(li.value);})
$('order_subtotal').value=subtotal;
$('subtotal').value=subtotal;
}
function updateTax() {
/* Tax rate is 7% */
var tax = ((parseFloat($('order_subtotal').value) +
parseFloat($('order_artwork').value) + parseFloat($('order_setup').value) +
parseFloat($('order_shipping').value) + parseFloat($('order_printing_charge').value)
+ parseFloat($('order_misc_charge').value)) * 0.07).toFixed(2);
$('order_tax').value=tax;
$('tax').value=tax;
}
function updateTotal() {
var total = (parseFloat($('order_subtotal').value) +
parseFloat($('order_artwork').value) + parseFloat($('order_setup').value) +
parseFloat($('order_tax').value) + parseFloat($('order_shipping').value) +
parseFloat($('order_printing_charge').value) +
parseFloat($('order_misc_charge').value)).toFixed(2);
$('order_total').value=total;
$('total').value=total;
}
function updateBalance() {
balance = $('order_total').value;
$('order_balance').value = balance;
$('balance').value = balance;
}
function recalculateTotals() {
updateTax();
updateTotal();
updateBalance();
}
There are no comments.
Leave a Comment

