czwartek, 1 grudnia 2011

Dynamiczne dodawanie opcji do kontrolki "select"

Wydawałoby się, że jest to prosta i jakże częsta akcja twórców stron i aplikacji internetowych. Jednak nie udało mi się znaleźć żadnego(sic!) rozwiązania, które byłoby zgodne z duchem Rails. Jak nie ma, to trzeba napisać. Siadłem i popełniłem kod jak poniżej.

Założenia są proste (podam w składni Rails) jest jakaś lista select, na przykład:
<%= select_tag :event_container %>
i chciałbym do tej listy dorzucać pracowników z listy
<%= select_tag(:employee_id,
                options_for_select(Employee.all.map { |employee| [employee.full_name, employee.id] }) %>
Chcę sobie kliknąć na listę z pracownikami, a po kliknięciu zaznaczony pracownik ma "przeskoczyć" z jednej listy na drugą. "Przeskoczyć" to znaczy zniknąć z jednej listy, a pojawić się na drugiej i vice versa.
Do widoku wrzucam więc obserwatory:
<%= observe_field "employee_id",
                   :url => {:action => :move_employee_name},
                   :with => "employee_id" %>
<%= observe_field "event_container",
                   :url => {:action => :remove_employee_from_event},
                   :with => "event_container" %>
i tworzę dla nich pliki RJS odpowiednio: move_employee_name.rjs i remove_employee_from_event.rjs. Muszą one być skorelowane z metodami o takich samych nazwach w kontrolerze! Nie zapomnieć!
Wpisy w move_employee_name.rjs dotyczą oczywiście kontrolek select:
page[:event_container].addNewOption @employee.full_name, @employee.id
page[:employee_id].removeSelectedOption
Wpisy w remove_employee_from_event.rjs są analogiczne
page[:employee_id].addNewOption @employee.full_name, @employee.id
page[:event_container].removeSelectedOption

I tu pytanie za 12 punktów: skąd RJS zna te dziwne metody? Mowa oczywiście o ".addNewOption" i ".removeSelectedOption". I od razu odpowiedź: wpisałem je w /public/javascripts/application.js w postaci:
/* Dodawanie metod do szablonów RJS. */
Element.addMethods({
 /* Dodawanie nowej opcji do kontrolki select. W tym przypadku jest to "element". */
 addNewOption: function(element, option, id) {
  element.options.add(new Option(option, id));
  return element;
 },
 /* Usuwanie zaznaczonej opcji w kontrolce select. Bez parametrów, gdyż funkcja leci po wszystkich. */
 removeSelectedOption: function(element) {
  var i;
  for (i = element.length - 1; i >= 0; i--) {
   if (element.options[i].selected) {
    element.remove(i);
    return;
   }
  }
 }
});
Tam jest właśnie cała magia :-)

Sposób opisany tu przeze mnie jest jak najbardziej zgodny z ideą Rails. Daje się łatwo utrzymać w kodzie aplikacji i jest czytelny. Nic tylko brać!