BanditHijo.dev

Membuat Collection of Checkbox yang Berbasis Rentang pada Rails

Created at: 2020-02-05
Author by: BanditHijo

Prerequisite

ruby 2.6.3 rails 5.2.3 postgresql 11.5

Prakata

Catatan kali ini, saya akan kembali mendokumentasikan tentang search filter yang menggunakan Ransack sebagai backend dengan tampilan frontend berupa checkbox.

Gambar 1

Gambar 1. Rails view helper collection_check_boxes

Seperti ilustrasi di atas, dapat teman-teman lihat, bahwa collection checkbox pada search filter panel tersebut memiliki rentang tertentu pada setiap listnya.

Hampir sama dengan tulisan saya sebelumnya, mengenai “Membuat Input Select yang Berbasis Rentang pada Rails”, yang mana pada tulisan tersebut, saya menggunakan view helper berupa select, kali ini saya akan menggunakan view helper berupa collection_check_boxes.

Secara default, collection_check_boxes ini dapat menampung multiple value yang akan disimpan dalam bentuk array. Namun, untuk menyederhanakan proses pencarian, saya membuat collection_check_boxes hanya dapat dipilih satu saja dengan bantuan jQuery.

Pemecahan Masalah

Masih sama seperti post sebelum ini.

Saya memanfaatkan Ransack sebagai search filter.

Ransack memiliki option pencarian untuk menghandle rentang, yaitu _in.

_in, match any values in array.

Selain array, dapat juga berupa tipe data range x..y.

Misal, seperti ilustrasi di atas.

Saya memiliki field bernama price (budget) dan duration_id.

Nah, sekarang saya akan mencoba bermain dengan Active Record.

1irb(main):001:0> Experience.ransack(price_in: 100..300).result.pluck(:price).uniq
1 (5.5ms) SELECT "experiences"."price" FROM "experiences" LEFT OUTER JOIN "ratings" ON "ratings"."experience_id" = "experiences"."id" WHERE "experiences"."deleted_at" IS NULL AND "experiences"."price" IN ('100', '101', '102', '103', '104', '105', '106', '107', '108', '109', '110', '111', '112', '113', '114', '115', '116', '117', '118', '119', '120', '121', '122', '123', '124', '125', '126', '127', '128', '129', '130', '131', '132', '133', '134', '135', '136', '137', '138', '139', '140', '141', '142', '143', '144', '145', '146', '147', '148', '149', '150', '151', '152', '153', '154', '155', '156', '157', '158', '159', '160', '161', '162', '163', '164', '165', '166', '167', '168', '169', '170', '171', '172', '173', '174', '175', '176', '177', '178', '179', '180', '181', '182', '183', '184', '185', '186', '187', '188', '189', '190', '191', '192', '193', '194', '195', '196', '197', '198', '199', '200', '201', '202', '203', '204', '205', '206', '207', '208', '209', '210', '211', '212', '213', '214', '215', '216', '217', '218', '219', '220', '221', '222', '223', '224', '225', '226', '227', '228', '229', '230', '231', '232', '233', '234', '235', '236', '237', '238', '239', '240', '241', '242', '243', '244', '245', '246', '247', '248', '249', '250', '251', '252', '253', '254', '255', '256', '257', '258', '259', '260', '261', '262', '263', '264', '265', '266', '267', '268', '269', '270', '271', '272', '273', '274', '275', '276', '277', '278', '279', '280', '281', '282', '283', '284', '285', '286', '287', '288', '289', '290', '291', '292', '293', '294', '295', '296', '297', '298', '299', '300')
2
3=> ["250", "150"]
1irb(main):002:0> Experience.ransack(duration_id_in: 1..8).result.pluck(:duration_id).uniq
1 (2.1ms) SELECT "experiences"."duration_id" FROM "experiences" LEFT OUTER JOIN "ratings" ON "ratings"."experience_id" = "experiences"."id" WHERE "experiences"."deleted_at" IS NULL AND "experiences"."duration_id" IN (1, 2, 3, 4, 5, 6, 7, 8)
2
3=> [8, 6, 4, 5, 7, 3, 2]

Kira-kira seperti itu.

Tinggal dibuatkan frontend nya.

Controller

Search filter ini akan tampil pada halaman Experience index.

app/controllers/experiences_controller.rb
1class ExperiencesController < ApplicationController
2 def index
3 @search = Experience.ransack(params[:q])
4 @experiences = @search.result(distinct: true).order(:id)
5 end
6
7 # ...
8 # ...
9end

Routes

Seperti biasa, saya memberikan route untuk action index.

config/routes.rb
1Rails.application.routes.draw do
2 resources :experiences, only: %w[index ... ...] do
3 # ...
4 # ...
5end

View Template

Contoh blok html di bawha ini hanya sebagai dummy.

Hanya blok kode ERB saja yang perlu diperhatikan.

app/views/experiences/index.html
1<%= search_form_for @search, url: experiences_path do |f| %>
2 <div class="position-relative">
3 <div class="row column-search">
4 ...
5 ...
6 ...
7
8 <!-- Budget Range Collection Check Boxes -->
9 <div class="border-bottom" id="filter-tour-budget">
10 <div class="d-flex justify-content-between ">
11 <span>Tour Budget</span>
12 <a class="font-size-12 clear-all" data-clear="filter-tour-budget">
13 <div id="clearAllBudget">clear all</div>
14 </a>
15 </div>
16 <div class="checkbox-box" id="budget-checkbox">
17 <%= f.collection_check_boxes :price_in,
18 [['Below RM100', 1..100],
19 ['RM101 - RM300', 101..300],
20 ['RM301 - RM500', 301..500],
21 ['RM501 and above', 501..1000]],
22 :second, :first,
23 checked: "#{(params[:q][:price_in] rescue nil)}" do |b| %>
24 <div class="form-check d-flex">
25 <%= b.check_box class: 'custom-control-input budget', name: 'q[price_in]' %>
26 <%= b.label class: 'custom-control-label' %>
27 </div>
28 <% end %>
29 </div>
30 </div>
31 <!-- END Budget Range Collection Check Boxes -->
32
33 <!-- Duration Collection Check Boxes -->
34 <div class="border-bottom" id="filter-tour-duration">
35 <div class="d-flex justify-content-between ">
36 <span>Tour Duration</span>
37 <a class="font-size-12 clear-all" data-clear="filter-tour-duration">
38 <div id="clearAllDuration">clear all</div>
39 </a>
40 </div>
41 <div class="checkbox-box" id="duration-checkbox">
42 <%= f.collection_check_boxes :duration_id_in,
43 [['< 4 hours', 1..8],
44 ['4 - 6 hours', 8..12],
45 ['> 6 hours', 12..24]],
46 :second, :first,
47 checked: "#{(params[:q][:duration_id_in] rescue nil)}" do |b| %>
48 <div class="form-check d-flex">
49 <%= b.check_box class: 'custom-control-input duration', name: 'q[duration_id_in]' %>
50 <%= b.label class: 'custom-control-label' %>
51 </div>
52 <% end %>
53 </div>
54 </div>
55 <!-- END Duration Collection Check Boxes -->
56
57 </div>
58 </div>
59
60 <div>
61 <%= f.submit "Check Availability", class: "btn btn-primary" %>
62 </div>
63<% end %>

Perhatikan pada masing-masing check box, saya memberikan nama,

1name: 'q[price_in]'
1name: 'q[duration_id_in]'

Agar output dari params tersebut menghasilkan string.

Karena, apabila saya tidak saya memberikan nama, check box ini secara default akan memiliki nama,

1name: 'q[price_in][]'
1name: 'q[duration_id_in][]'

Yang akan menghasilkan params dengan value berupa array.

Maka dari itu, pada controller, saya perlu membuat sebuah method yang akan mengkonversi dari string menjadi range.

Karena nanti hasil output dari params[:q][:price_in] dan params[:q][:duration_id_in] dari view helper collection_check_boxes berupa string, maka saya perlu merubah tipe datanya menjadi range pada experiences_controller.

Tambahkan kode di bawah ini pada experiences_controller.rb.

app/controllers/experiences_controller.rb
1class ExperiencesController < ApplicationController
2 before_action :convert_string_into_range, only: [:index]
3
4 def index
5 # ...
6 end
7
8 # ...
9 # ...
10
11 private
12
13 # For converting string of value params into range
14 def string_to_range(rangestr)
15 rangestr&.split('..')&.inject { |s,e| s.to_i..e.to_i }
16 end
17
18 def convert_string_into_range
19 # For Experiences, search filter by budget range. Convert string into range
20 unless (params[:q][:price_in] rescue nil).blank?
21 params[:q][:price_in] = string_to_range(params.dig(:q, :price_in))
22 end
23
24 # For Experiences, search filter by duration range. Convert string into range
25 unless (params[:q][:duration_id_in] rescue nil).blank?
26 params[:q][:duration_id_in] = string_to_range(params.dig(:q, :duration_id_in))
27 end
28 end
29end

jQuery disable multiple check

Secara default, view helper collection_check_boxes ini dapat menampung multiple value yang disimpan dalam array, sehingga frontend, kita dapat memilih banyak pilihan pada check box. Namun, karena alasan tertentu, saya memilih untuk menyederhanakan dan membuat collection_check_boxes ini hanya dapat menampung nilai string dan hanya dapat memilih satu saja.

Saya menggunakan bantuan jQuery untuk dapat melakukan hal tersebut di atas.

app/views/experiences/index.html
1...
2...
3
4<script>
5 // ...
6 // ...
7
8 // For clear all checkboxes on Duration
9 $('#clearAllDuration').on("click", function(){
10 var clearCheckBox = $('input[class="custom-control-input duration"]');
11 clearCheckBox.prop("checked",false);
12 });
13
14 // For clear all checkboxes on Budget
15 $('#clearAllBudget').on("click", function(){
16 var clearCheckBox = $('input[class="custom-control-input budget"]');
17 clearCheckBox.prop("checked",false);
18 });
19
20 // For Duration only able to select on checkbox and uncheck other
21 $('input.custom-control-input.duration').on('change', function() {
22 $('input.custom-control-input.duration').not(this).prop('checked', false);
23 });
24
25 // For Budget only able to select on checkbox and uncheck other
26 $('input.custom-control-input.budget').on('change', function() {
27 $('input.custom-control-input.budget').not(this).prop('checked', false);
28 });
29
30 // ...
31 // ...
32</script>

Nah, dengan begini tahapan demi tahapan sudah selesai.

Mudah-mudahan dapat bermanfaat buat teman-teman.

Terima kasih.

(^_^)

Referensi

  1. apidock.com/rails/v4.0.2/ActionView/Helpers/FormOptionsHelper/collection_check_boxes
    Diakses tanggal: 2020/02/05

  2. github.com/activerecord-hackery/ransack/wiki/Basic-Searching#in
    Diakses tanggal: 2020/02/05