Rails: Selección de menús dinámicos dentro de Active Admin


rubyonrails Actualmente estoy desarrollando un nuevo proyecto de Ruby on Rails,  versión 4 de Rails y versión 2 de Ruby. En esta ocasión quería compartir una parte que he tenido que desarrollar y en la que he encontrado mucha materia pero que no me ha ayudado en esta ocasión demasiado.

El problema

En la parte del backoffice de un portal para la que estoy trabajando para nuestro cliente, estoy utilizando Active Admin. El caso es que esta es una particularidad a la hora de hacer las cosas, en este caso tengo una serie de select independientes de País, Cuidad y Población. Cuando tienes que seleccionar cada uno de ellos individualmente los seleccionas, esto es un poco feo por decirlo de alguna manera. Lo suyo sería una vez que secciones el País, deberían seleccionarse cada una de las provincias y ciudades, en un orden alfabético, después ya si necesitas una provincia concreta, haces la sección de la provincia y automáticamente te selecciona las poblaciones. Una vez dicho esto, parece fácil el desarrollo de la solución, te pones a ver que hay de ejemplos y encuentras muchos recursos unos mejores y otros muy poco útiles. Por eso me he puesto a explicar que solución he desarrollado, por si te encuentras en esta situación tengas un punto dónde poder tener los pasos de la solución y para ello voy a explicarlo lo mejor posible. Por ejemplo he visto un railscasts sobre este tema un poco viejo aunque hay una de pago actualizada, pero lo fuentes están aquí, en stackoverflow también hay mucho pero te dejo algunos de los que he visto, como este y este otro también por si te son de utilidad.

Solución

Como ya he comentado he tenido que utilizar Active Admin para el backoffice y esto requiere hacerlo a la forma de Active Admin, para eso tenemos  que ir a la parte de app/admin y en el fichero en el que queramos meter la nueva funcionalidad. Dentro nos encontraremos los Filter, Permited, Index, Show, Form…..

Pues si tenemos en nuestro formulario de edición los elementos que necesitamos que cambien:

form do |f|
       f.inputs do
       f.input :name
       f.input :external_id
       f.input :type_id, as: :select, collection: CenterType.for_select, include_blank: false

……………………..

       f.inputs Address.model_name.human, for: [:address_attributes, f.object.address] do |fa|
            fa.input :id, as: :hidden
            fa.input :country_id, as: :select, collection: Country.all.order(:name), label: Address.human_attribute_name(‘country_id’)
            fa.input :province_id, as: :select, collection: Province.all.order(:name), label: Address.human_attribute_name(‘province_id’)
            fa.input :city_id, as: :select, collection: City.all.order(:name), label: Address.human_attribute_name(‘city_id’)
            fa.input :name, label: Address.human_attribute_name(‘name’)
      end

Lo primero que tenemos que pensar es que a nuestra solución va ligada a una acción y esto requiere que hagamos un controlador con su acción correspondiente. En nuestro caso deberíamos meter un controlador de Active Admin de la siguiente manera:

collection_action :change_provinces, method: :get do
        provinces = Province.where(country_id: params[:country_id])
        render json: provinces.map { |c| c.as_json(only: [:id, :name]) }
end

Una vez que ya tenemos nuestro controlador collection_action nos aseguramos que vamos por el buen camino, tenemos nuestro controlador change_provinces con el método get en el nos va ha lanzar una consulta para saber todas las provincias con el país que le pasamos por parámetro, es decir, tenemos España id 111 por ejemplo, nos va ha seleccionar todas las ciudades en el formato indicado de JSON y los resultados los tenemos que llevar al select correspondiente. Ahora es cuando llega lo bueno, cuando cambie el evento de los select que queremos, País y el de provincias, deberemos cambiar los datos de cada una de las select. El primer camino abierto hacia una solución fue entrar en el evento onchange del formulario del input que estaba tratando:

fa.input :country_id, as: :select,
              collection: Country.all, label: Address.human_attribute_name(‘country_id’),
               input_html: {
                          onchange: remote_request(:get, polymorphic_path([:change_provinces, :admin, :centers]),
                          { country_id: “$(‘#center_address_attributes_country_id’).val()”}, :country_id)
               }

Pero este camino lo he descartado por completo, a no ser que me hagas cambiar de opinión al respecto y te explico mis razones. Cuando salte el evento onchange el método que lo he metido en application helper me coloca los elementos correspondientes para la llamada a nuestro controlador change_provinces y el parámetro correspondiente del id del país que está seleccionado.

def remote_request(type, path, params={}, target_tag_id)
         “$.#{type}(‘#{path}’,
          {#{params.collect { |p| “#{p[0]}: #{p[1]}” }.join(“, “)}},
               function(data) {$(‘##{target_tag_id}’).html(data);}
           );”
end

Pero cuando llegamos al punto final que tenía puesto render :text=>view_context.options_from_collection_for_select(provinces, :id, :name), el tema es dónde coloco esto con el view_context? pues como no tengo idea, he tenido que tomar otro camino que en principio me pareció mejor, pero por ver que resultado tenía con este otro, probé.

La mejor opción es tratarlo por AJAX el mismo evento onchange:

$(document).ready(function(){
           $(“select#center_address_attributes_country_id”).change(function(){   <=== capturamos el vento onchange dentro del selecctor ID
          // Send the request and update provinces dropdown
          $.ajax({
                  dataType: “json”,
                  cache: false,
                  url: ‘mi ruta correspondiente/change_provinces?country_id=’ + $(‘#center_address_attributes_country_id :selected’).val(), <=== Hacemos la llamada a nuestro controlador en admin
                  timeout: 2000,
                  error: function(XMLHttpRequest, errorTextStatus, error){
                           alert(“Failed to submit : “+ errorTextStatus+” ;”+error);
                 },
                 success: function(data){
                            // Clear all options from provinces and cities select
                            $(“select#center_address_attributes_province_id option”).remove(); <== Eliminamos los datos que tenga
                            $(“select#center_address_attributes_city_id option”).remove(); <== Eliminamos los datos que tenga
                            var row;
                            $(row).appendTo(“select#center_address_attributes_province_id”); <== por si queremos meter un elemento primero vacío
                            // New provinces select
                            $.each(data, function(i, j){
                                      row = “<option value=\”” + j.id + “\”>” + j.name + “</option>”;
                                      $(row).appendTo(“select#center_address_attributes_province_id”); <=== Vamos incorporando los nuevos
                                     });
                    

  

Finalmente si no te está tomando bien las rutas de active admin puedes incorporarlas a mano en routes:

get “/mi ruta correspondiente/:country_id” => ‘admin/center#change_provinces’

En los casos en los que necesites disponer de select pero el número de opciones es demasiado alto como para soportar en un select, estoy hablando de 15 o 20 mil elementos en el select, esto no es muy viable que digamos. Comentaré en un próximo post como he resuelto el tener que utilizar como elemento de selección esta cantidad de datos.

Un comentario en “Rails: Selección de menús dinámicos dentro de Active Admin

  1. Juanma dijo:

    hola Carlos,
    esta ruta ‘mi ruta correspondiente/change_provinces’ se la deberias de pasar al js como un attributo ‘data’ del elemento select, y darle valor en rails al pintarlo. Asi te aseguraras que siempre es la ruta correcta aunque por ejemplo cambiases el routes.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s