Di recente ho dovuto implementare in una view la classica ricerca con due select dipendenti l’una dall’altra. Volevo però lasciare il template .erb il più possibile pulito e rendere il codice che carica le option della select figlia il più possibile riutilizzabile e generico.
Supponiamo dunque di avere due modelli, SpecializationType e Specialization, così composti:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class SpecializationType < ActiveRecord::Base
# FIELDS
# RELATIONS
has_many :specializations
# TRIGGERS
# VALIDATIONS
validates :name, :presence => true
validates :description, :presence => true
# SCOPES
# OTHER
def to_s
name
end
end
class Specialization < ActiveRecord::Base
# FIELDS
# RELATIONS
belongs_to :specialization_type
has_and_belongs_to_many :users
# TRIGGERS
# VALIDATIONS
validates :name, :presence => true
validates :description, :presence => true
validates :specialization_type, :presence => true
# SCOPES
# OTHER
def to_s
name
end
end
Come si puo’ notare, Specialization dipende dalla SpecializationType che andremo a selezionare.
Nel nostro template, creeremo le due dropdown in questo modo:
2
3
4
5
6
7
8
9
10
11
<%= form_tag users_path, {:method => :get, :class => "users_search_form"} do %>
<%= select_tag :specialization_type_id, options_from_collection_for_select(SpecializationType.all, "id", "name"), :prompt => "Select a specialization type" %>
<%= select_tag :specialization_id, options_from_collection_for_select([], "id", "name"),
"data-option-dependent" => true,
"data-option-observed" => "specialization_type_id",
"data-option-url" => "/specialization_types/:specialization_type_id:/specializations.json",
"data-option-key-method" => :id,
"data-option-value-method" => :name %>
<br/>
<%= submit_tag "Cerca" %>
<% end %>
Come si puo’ notare, la dropdown figlia indica in una serie di “data-option” tutto il necessario per poter caricarsi a runtime gli elementi.
In particolare:
“data-option-dependent” => true, indica che questa dropdown dipende da una dropdown padre
“data-option-observed” => “specialization_type_id”, specifica l’id della dropdown da monitorare. Il suo cambio di valore scatenerà la richiesta al server
“data-option-url” => “/specialization_type/:specialization_type_id:/specializations.json”, è l’url che andrò a chiamare sul server. Lo script andrà a sostituire :specialization_type_id: con l’id dell’elemento selezionato nella dropdown padre
“data-option-key-method” => :id, indica che userò il campo ID del json tornato dal server come attrbuto “chiave” della < option >
“data-option-value-method” => :name, indica che userò il campo NAME del json tornato dal server come attrbuto “valore” della < option >
Aggiungiamo poi in application.js il seguente codice per gestire tutte le dropdown dipendenti:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
jQuery(document).ready(function () {
$('select[data-option-dependent=true]').each(function (i) {
var observer_dom_id = $(this).attr('id');
var observed_dom_id = $(this).data('option-observed');
var url_mask = $(this).data('option-url');
var key_method = $(this).data('option-key-method');
var value_method = $(this).data('option-value-method');
var prompt = $(this).has('option[value=]').size() ? $(this).find('option[value=]') : $('<option value=\"\">').text('Select a specialization');
var regexp = /:[0-9a-zA-Z_]+:/g;
var observer = $('select#' + observer_dom_id);
var observed = $('#' + observed_dom_id);
if (!observer.val() && observed.size() > 1) {
observer.attr('disabled', true);
}
observed.on('change', function () {
observer.empty().append(prompt);
if (observed.val()) {
url = url_mask.replace(regexp, observed.val());
$.getJSON(url, function (data) {
$.each(data, function (i, object) {
observer.append($('<option>').attr('value', object[key_method]).text(object[value_method]));
observer.attr('disabled', false);
});
});
}
});
});
});
Basterà ora definire nel file routes.rb la seguente rotta:
get "specialization_types/:specialization_type_id/specializations" => "application#specializations", :as => "specializations", :format => :json
Ed infine implementare il metodo nel controller:
2
3
4
5
6
def specializations
specialization_type = SpecializationType.find(params[:specialization_type_id])
respond_to do |format|
format.json { render :json => specialization_type.specializations }
end
end