viernes, 17 de julio de 2009

Combos dependientes re-visited

Hace tiempo ya conté cómo hacer combos dependientes con observe_field pero debe ser que me hago viejo y que me gusta hacer las cosas cada vez más pedrestres. En fin, que tirando a pelo de prototype, un poco de javascript y adaptando un controlador, conseguimos lo mismo de forma muy sencilla.

Supongamos dos modelos dependientes

class Country < ActiveRecord::Base
 has_many :provinces
end

class Province < ActiveRecord::Base
 belongs_to :country
end

enlazados en nuestro archivo de rutas como

map.resources :countries do |country|
 country.resources :provinces
end

En a vista enlazamos países y provincias en dos combos...

<%= select :person, :country_id, countries_for_select.map{|c| [c.name, c.id]}, {}, {:onchange => "change_provinces('person_province_id', this.value);", :class => 'w150'} %>
<%= select :person, :province_id, provinces_for_select.map{|c| [c.name, c.id]}, {}, {:class => 'w150'} %>

donde countries_for_select y provinces_for_select nos dan los registro a pintar inicialmente en cada uno de ellos.

Sólo falta la función javascript change_provinces que haga la llamada Ajax y el repintado del combo cuyo ide recibe como primer parámetro...

function change_provinces(combo_id, country_id){
 var url = '/countries/' + country_id + '/provinces';
 var combo = $(combo_id);
 for (var i = combo.options.length-1; i >= 0; i--){
  combo.options[i] = null;
 }
 new Ajax.Request(url, {
  method: 'get',
  onSuccess: function(transport) {
   prov_array = eval(transport.responseText);
   for (var k = 0; k < prov_array.length; k++){
    combo.options[k] = new Option(prov_array[k][1], prov_array[k][0]);
   }
   combo.options[0].selected = true;
  },
  onFailure:function(transport) {
  }
 });
}

... y finalmente adaptar provinces_controller

def index
 @provinces = ...
 if request.xhr?
  render :text => @provinces.map{|province| [province.id, province.name]}.inspect
  return
 end
end

Y a correr. Ale, salud y rocanrol!

We shall go on to the end, we shall fight in France,
we shall fight on the seas and oceans,
we shall fight with growing confidence and growing strength in the air,
we shall defend our Island, whatever the cost may be,
we shall fight on the beaches,
we shall fight on the landing grounds,
we shall fight in the fields and in the streets,
we shall fight in the hills, we shall never surrender!"