求助:如何用Rails 5 + AJAX实现按地点筛选课程的三级联动下拉菜单
Hey Bradley, let's get that dependent dropdown workflow working smoothly! Since you're using Rails, we can combine its backend tools with some jQuery AJAX to make the dropdowns update dynamically based on user selections. Here's a step-by-step breakdown tailored to your setup:
First, we need to modify your ERB view to add unique IDs to each dropdown (so we can target them with JS) and set sensible initial states (disabled until a parent selection is made):
<div class="row no-gutters wow slideInUp" data-wow-duration="1s"> <div class="col-md-12 home-form"> <form class="form-inline"> <!-- Location dropdown: Add ID to trigger AJAX on change --> <select id="location-select" class="custom-select mb-0 mr-sm-0 mb-sm-0"> <option selected>Location</option> <% @locations.each do |location| %> <option value="<%= location.id %>"><%= location.header %></option> <% end %> </select> <!-- Course Type dropdown: Start disabled, empty except default --> <select id="course-type-select" class="custom-select mb-0 mr-sm-0 mb-sm-0" disabled> <option selected>Course Type</option> </select> <!-- Course dropdown: Start disabled, empty except default --> <select id="course-select" class="custom-select mb-0 mr-sm-0 mb-sm-0" disabled> <option selected>Course</option> </select> <button type="submit" class="btn btn-primary">Submit</button> </form> </div> </div>
Next, we'll add two controller actions to return filtered data as JSON, plus the routes to map to them.
Controller Actions (Add to your CoursesController)
These actions will fetch the relevant data based on the user's selection:
# app/controllers/courses_controller.rb # Get unique course types for a given location def course_types_by_location location = Location.find(params[:location_id]) # Pluck unique course types and sort them for better UX course_types = location.courses.pluck(:course_type).uniq.sort render json: { course_types: course_types } end # Get courses for a given location + course type def courses_by_type_and_location location = Location.find(params[:location_id]) courses = location.courses.where(course_type: params[:course_type]) # Format courses into a simple ID/title structure for the dropdown course_options = courses.map { |course| { id: course.id, title: course.title } } render json: { courses: course_options } end
Routes (Add to config/routes.rb)
Map the actions to accessible URLs:
get 'courses/course_types_by_location', to: 'courses#course_types_by_location' get 'courses/courses_by_type_and_location', to: 'courses#courses_by_type_and_location'
Create a new file at app/assets/javascripts/course_dropdowns.js (or add this to your existing JS file) to handle the dynamic updates:
$(document).ready(function() { // Handle location selection change $('#location-select').on('change', function() { const locationId = $(this).val(); // Reset dropdowns if user picks the default "Location" option if (!locationId) { $('#course-type-select').empty().append('<option selected>Course Type</option>').prop('disabled', true); $('#course-select').empty().append('<option selected>Course</option>').prop('disabled', true); return; } // Fetch course types for the selected location $.ajax({ url: '/courses/course_types_by_location', type: 'GET', data: { location_id: locationId }, beforeSend: function() { // Show loading state while fetching $('#course-type-select').prop('disabled', true).empty().append('<option selected>Loading...</option>'); }, success: function(response) { // Enable and populate the course type dropdown $('#course-type-select').prop('disabled', false).empty().append('<option selected>Course Type</option>'); response.course_types.forEach(function(type) { $('#course-type-select').append(`<option value="${type}">${type}</option>`); }); // Reset course dropdown since location changed $('#course-select').empty().append('<option selected>Course</option>').prop('disabled', true); }, error: function() { alert('Oops, failed to load course types. Please try again.'); // Reset to default state on error $('#course-type-select').empty().append('<option selected>Course Type</option>').prop('disabled', true); } }); }); // Handle course type selection change $('#course-type-select').on('change', function() { const locationId = $('#location-select').val(); const courseType = $(this).val(); // Reset course dropdown if user picks default if (!courseType) { $('#course-select').empty().append('<option selected>Course</option>').prop('disabled', true); return; } // Fetch courses for the selected location + type $.ajax({ url: '/courses/courses_by_type_and_location', type: 'GET', data: { location_id: locationId, course_type: courseType }, beforeSend: function() { $('#course-select').prop('disabled', true).empty().append('<option selected>Loading...</option>'); }, success: function(response) { // Enable and populate the course dropdown $('#course-select').prop('disabled', false).empty().append('<option selected>Course</option>'); response.courses.forEach(function(course) { $('#course-select').append(`<option value="${course.id}">${course.title}</option>`); }); }, error: function() { alert('Oops, failed to load courses. Please try again.'); $('#course-select').empty().append('<option selected>Course</option>').prop('disabled', true); } }); }); // Handle form submission to navigate to the course page $('form').on('submit', function(e) { e.preventDefault(); // Stop default form reload const courseId = $('#course-select').val(); if (courseId) { // Redirect to the course's show page (adjust the path if your routes use a different structure) window.location.href = `/courses/${courseId}`; } else { alert('Please select a course first!'); } }); });
What This JS Does:
- Listens for changes to the location dropdown, fetches matching course types, and populates the second dropdown.
- Listens for changes to the course type dropdown, fetches matching courses, and populates the third dropdown.
- Adds loading states to let users know data is being fetched.
- Handles form submission by redirecting to the selected course's page instead of reloading the whole page.
- Make sure jQuery is included in your Rails app (it's included by default in older Rails versions; for Rails 7+, you may need to add it via
importmap-railsor a CDN). - If your
Coursemodel doesn't have acourse_typeattribute, adjust the controller actions to match your actual course categorization logic. - You can style the loading states further (like adding a spinner icon) to improve UX even more.
内容的提问来源于stack exchange,提问作者Bradley

