Ví dụ tự tạo Add và Remove select box với jquery và nested attributes
Bài đăng này đã không được cập nhật trong 4 năm
Khi chúng ta muốn tạo 1 multiselect box với 2 box Remove và Add rõ ràng như sau:
Thì đã có rất nhiều các thư viện js hỗ trợ điển hình là Jquery multiselect.js.
Nhưng mình muốn tự làm 1 cái đơn giản chỉ với Jquery và sẽ kết hợp với nested_attributes của rails luôn.
Ví dụ mình có 1 bảng Exam và 1 bảng Question có quan hệ n-n như sau:
#models/exams.rb
class Exam < ApplicationRecord
has_many :exam_questions, dependent: :destroy
has_many :questions, through: :exam_questions
end
#models/exam_questions.rb
class ExamQuestion < ApplicationRecord
belongs_to :question
belongs_to :exam
end
#models/questions.rb
class Question < ApplicationRecord
has_many :exam_questions, dependent: :destroy
has_many :exams, through: :exam_questions
scope :by_not_ids, -> (ids){where.not(id: ids)}
# mình sẽ dùng scope trên để xíu nữa lấy ra các question chưa được thêm vào đề thi
end
Và mình muốn khi tạo Exam thì sẽ có thể chọn được các question vào exam đó luôn. Nên mình sẽ thêm accepts_nested_attributes_for
trong exam_questions.rb
#models/exam_questions.rb
class ExamQuestion < ApplicationRecord
belongs_to :question
belongs_to :exam
accepts_nested_attributes_for :exam_questions, allow_destroy: true
end
Trong exams_controller.rb chỉ cần như sau:
class ExamsController < ApplicationController
before_action :load_exam, except: %i(index create new)
def new
@exam = Exam.new
respond_to do |format|
format.js
format.html
end
end
def create
@exam = Exam.new exam_params
if @exam.save
flash[:success] = "Tạo đề thi thành công"
redirect_to admin_exams_path
else
flash.now[:danger] = "Tạo đề thi thất bại"
respond_to do |format|
format.js
end
end
end
def update
if @exam.update_attributes exam_params
flash[:success] = "Cập nhật đề thi thành công"
redirect_to admin_exams_path
else
flash.now[:danger] = "Cập nhật đề thi thất bại"
respond_to do |format|
format.js
end
end
end
def edit
end
private
def load_exam
@exam = Exam.find_by id: params[:id]
return if @exam
flash[:danger] = "Không tìm thấy đề thi"
redirect_to admin_exams_path
end
def exam_params
params.require(:exam).permit :title, exam_questions_attributes: %i(id question_id _destroy)
end
end
Trong ví dụ này mình sẽ chỉ tạo exam có 1 thuộc tính duy nhất là title
.
Trong views/exams/new.html.erb
:
<div class="block full">
<div class="block-title text-center">
<strong> Thêm mới đề thi </strong>
</div><br>
<%= render "form" %>
</div>
Trong views/exams/edit.html.erb
:
<div class="block full">
<div class="block-title text-center">
<strong> Sửa đề thi </strong>
</div><br>
<%= render "form" %>
</div>
và cuối cùng là views/exams/form.html.erb
:
<%= simple_form_for @exam, html: {id: "exam-form"} do |f| %>
<div class="row">
<div class="col-md-3">
<div class="pull-right">
<strong>Tên đề thi</strong>
</div>
</div>
<div class="col-md-7">
<%= f.input :title, label: false, placeholder: "Nhập tên bài thi" %>
</div>
</div>
<!-- select box Câu hỏi chưa được thêm vào đề thi -->
<div class="col-md-6">
<div class="block select-box-question avaiable-questions">
<div class="block-title text-center">
Danh sách câu hỏi có sẵn
</div>
<!-- by_not_ids để lấy ra danh sách câu hỏi chưa được thêm vào đề thi trong trường hợp edit exam -->
<% Question.by_not_ids(@exam.question_ids).each do |question| %>
<div class="block-question" data-question-id="<%= question.id %>">
<%= strip_tags(question.content).truncate(50) %>
</div>
<% end %>
</div>
</div>
<!-- select box Câu hỏi được thêm vào đề thi -->
<div class="col-md-6">
<div class="block select-box-question selected-questions">
<div class="block-title text-center">
Danh sách câu hỏi được chọn
</div>
<% @exam.exam_questions.each do |exam_question| %>
<div class="block-question remove-exam" data-id="<%= exam_question.id %>">
<%= strip_tags(exam_question.question.content).truncate(50) %>
</div>
<% end %>
</div>
</div>
<br>
<div class="text-center">
<%= f.button :button, type: "button", id: "submit-exam-btn", class: "btn btn-sm btn-primary", name: nil do %>
<i class="fa fa-floppy-o" aria-hidden="true"></i>
Lưu đề thi
<% end %>
</div>
<br>
<% end %>
Okay, giờ đến phần xử lý js:
// Xử lý click vào tên question thì sẽ nhảy từ box remove sang box add và ngược lại
$(document).on('click', '.block-question', function() {
if ($(this).parent('.avaiable-questions').length){
$('.selected-questions').append($(this))
$('.avaiable-questions').remove($(this))
}
else if ($(this).parent('.selected-questions').length){
$('.avaiable-questions').append($(this))
$('.selected-questions').remove($(this))
}
});
// Xử lý khi ấn nút tạo exam
$(document).on('click' ,'#submit-exam-btn', function() {
// lấy ra id của form
var form = $(this).closest('#exam-form');
// xử lý để phân biệt create và update exam
var url = form.attr('action');
var type = form.find('input[name="_method"]').val() || form.attr('method');
// lấy giá trị của ô input title
var title = $('#exam_title').val();
// khởi tạo 1 biến json để lưu thông tin question
var exam_questions_attributes = {}
// vòng lặp để lấy thông tin từng question trong "Add box" để lưu id(exam_question_id) và question_id vào biến exam_questions_attributes
$('.selected-questions .block-question').each(function(index){
exam_questions_attributes[index] = {"id": $(this).data('id'), "question_id": $(this).data('question-id')}
});
// cái này để lấy ra số question đã được lưu vào exam_questions_attributes
var number_questions_selected = Object.keys(exam_questions_attributes).length;
// vòng lặp để lấy thông tin từng question trong "Remove box" để lưu id(exam_question_id) và 1 biến mặc định _destroy = true(để đánh dấu cho nested_attributes biết phải xóa question_exam này) nối tiếp danh sách các "add question" trong biến exam_questions_attributes
$('.avaiable-questions .remove-exam').each(function(index){
exam_questions_attributes[index + number_questions_selected] = {"id": $(this).data('id'), "_destroy": true}
});
// Cuối cùng là gửi ajax các thông tin đã được xử lý đến controller để lưu thôi
$.ajax({
url: url,
type: type,
data: {
"exam[title]": title,
"exam[exam_questions_attributes]": exam_questions_attributes
}
});
});
Xong, và đây là những gì chúng ta có :
Tài liệu tham khảo
https://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html https://www.w3schools.com/js/js_json.asp
All rights reserved