Friday, August 26, 2016

Using Collection_Check_Boxes to Simplify User Interactions

Collection_check_boxes could be a very useful tool for simplify user interactions. Considering such scenario and table associations:

  • model
  • size
  • color
The model has size and color attributes. We hope to add and edit these three items on the same page in order to reduce complexity of operation.

First, the three models must have associations with each other.

model:
has_and_belongs_to_many :sizes
has_and_belongs_to_many :colors
size:
has_and_belongs_to_many :models
color:
has_and_belongs_to_many :models

Secondly, add collection_check_boxes. 

view/models/_form.html.erb
for “size” attribute
<%= f.collection_check_boxes :size_ids, Size.all, :id, :name do |b| %>
  <div class="collection-check-box">
    <%= b.check_box %>
    <%= b.label %>
    </div>
  <% end %>
for “color” attribute
<%= f.collection_check_boxes :color_ids, Color.all, :id, :name do |b| %>
  <div class="collection-check-box">
    <%= b.check_box %>
    <%= b.label %>
    </div>
  <% end %>
Thus, the “size” and “color” attributes can be added and edited in the same page along with “model”. It simplifies the user operation and make the app more intuitive.

Friday, August 19, 2016

Using jQuery Ajax in Rails Appliccations

Normally, we build a restful rails application when a ordering transaction system is needed. However, imaging a scenario such as a bicycle with three attributes:
  • model
  • size
  • color
The size and color options vary by model and they are not always the same combination across all models. For example, model A has size options of 700c X 48cm, 700c X 50cm and 700c X 52cm and color options of red, blue and black. Model B may have 700c X 48cm and 700c X 50cm and color options of green and orange. Therefore, when model changes during user operation, the size and color options must be changed accordingly.
That is a big design problem for a Rails application. Unless there is a a button for user to press and refresh the page while a model has been chosen, the Rails app has no idea when to refresh the page.
I use jQuery to solve this problem.
jQuery ajax can be used to listen the change of the model selection. When the model change happens, ajax calls the get_info function in orderdetails controller.
/view/orderdetails/new.html.erb
<script type="text/javascript">
$('#orderdetail_model_id').on('change', function() {
    $.ajax({
      url: '/get_info',
      type: 'GET',
      data: { model_id: this.value }
    })
  });
</script>  
The path is set in the routes.rb first.
get '/get_info', to: 'orderdetails#get_info', as: :get_info
Then ‘get_info’ function returns the instance variables needed for the page refresh.
controller orderdetails#get_info
def get_info
  @model  = Model.find(params[:model_id])
  @sizes  = @model.sizes
  @colors = @model.colors
  respond_to do |format|
    format.js
  end
end
Here is the magic: ‘get_info.js.erb’ replace the size and color select in their divs . The size and color options changes while model changes.
get_info.js.erb
$("#orderdetail_select_size_id").html("<%= escape_javascript(select("orderdetail", "size_id", @sizes.collect {|s| [s.name, s.id ] }, { include_blank: false })) %>");
$("#orderdetail_select_color_id").html("<%= escape_javascript(select("orderdetail", "color_id", @colors.collect {|c| [c.name, c.id ] }, { include_blank: false })) %>");
$("#orderdetail_text_field_price").html("<%= escape_javascript(text_field_tag("orderdetail", @model.price, style: "text-align: right", disabled: true )) %>");
TIP:
The code to be replaced need to be modified in the ‘get_info.js.erb’.
The ‘f.select’ causes error because ‘f’ is not defined in the replaced code portion.
f.select("size_id", Model.find_by_id(orderdetail.model_id).sizes.collect {|s| [ s.name, s.id ] }, { include_blank: false })
We have to replace it to the name of the select. By inspecting the generated html file, the name of the select is “orderdetail_select_size_id”. Using select_tag and name the variable as “orderdetail”, the problem solved and it works.
select("orderdetail", "size_id", @sizes.collect {|s| [s.name, s.id ] }, { include_blank: false }))"