Rails 6 Cascading Select List - PullRequest
       0

Rails 6 Cascading Select List

0 голосов
/ 25 марта 2020

В Rails 6 я хочу, чтобы в профилях / _form было два раскрывающихся списка: страна и город. Когда я выбираю значение из страны, это должно изменить выбор города. Я хочу, чтобы это произошло без обновления страницы. Мое решение ниже, и оно вроде работает для нового действия, но оно не работает для действия редактирования. Это правильный подход или я полностью упускаю решение idiomati c rails 6?

Маршрут для возврата тегов опций для поля выбора города:

# config/routes.rb
get 'cities_by_country/:id', to: 'profiles#cities_by_country'  

Действие, которое запускает

# profiles_controller
  def cities_by_country
    @city_list = Profile::CITY_LIST[params[:id].to_i]
    respond_to do |format|
      format.js { render :cities_by_country}
    end
  end

Файл js для генерации тегов параметров

#views/profiles/cities_by_country.js.erb
<%= options_for_select(@city_list) %>

javascript для добавления события «изменение» к тегу выбора страны:

# app/javascript/packs/country_cities.js
import Rails from '@rails/ujs';
var country_select, city_select, selected_country;
window.addEventListener("load", () => {
  country_select = document.querySelector("select#profile_country");
  city_select = document.querySelector("select#profile_city");
  country_select.addEventListener('change', (event) => {
    selected_country = country_select.selectedIndex;
    Rails.ajax({
      url: "/cities_by_country/" + selected_country,
      type: "get",
      data: "",
      success: function (data) {
        city_select.innerHTML = data;
       },
      error: function (data) { }
    })
  })
});

1 Ответ

1 голос
/ 25 марта 2020

Извините, что сложил это на вас, но это действительно сломано.

Давайте начнем с контроллера / маршрута. Идиоматический c способ сделать это по вложенному маршруту - GET /countries/:country_id/cities. Вы также не должны вносить это в свою модель Profile / ProfilesController.

Вы можете объявить маршрут с помощью:

get '/countries/:country_id/cities', to: 'countries/cities#index'

или с помощью resources с блоком:

resources :countries, only: [] do
  resources :cities, only: [:index], module: :countries
end

И контроллер вот так:

module Countries
  class CitiesController < ApplicationController
    # GET /countries/:country_id/cities
    def index
      @cities = Profile::CITY_LIST[params[:city_id].to_i] 
    end
  end
end

Не уверен, что я действительно могу понять, почему вы хотите использовать константу в модели, которая вообще не должна отвечать за это, а не на самом деле создание моделей "Страна" и "Город".

Самая большая проблема с вашим JavaScript заключается в том, что он абсолютно неидемпотентен. Все это работает на window.addEventListener("load"), так что оно работает при начальной загрузке страницы, а затем полностью выходит из строя, когда Turbolinks заменяет содержимое страницы на AJAX, так как эти обработчики событий были присоединены непосредственно к самим элементам.

Чтобы написать JavaScript, который работает с Turbolinks, вы должны думать иначе. Создайте идемпотентные обработчики, которые улавливают событие, когда оно всплывает в DOM.

# app/javascript/packs/country_cities.js
import Rails from '@rails/ujs';

document.addEventListener('change', (event) => {
  let input = event.target;
  if (input.matches('#profile_country')) {
    Rails.ajax({
      url: `/cities/${input.value}/country`,
      type: 'get',
      dataType : 'script'
    });
  }
});

Если вы хотите использовать шаблон js.erb, вам также необходимо переписать ваше представление так, чтобы оно трансформировало страницу:

// app/views/countries/cities.js.erb
document.getElementById("#profile_city").innerHTML = "<%= j options_for_select(@cities) %>";

Но вы также можете просто использовать JSON и создать элементы опции на клиенте, если хотите избежать ответственности сервера за преобразования на стороне клиента.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...